Commit 1c0b528a authored by louiz’'s avatar louiz’

Update the database code to use odb

parent ecf55530
......@@ -23,7 +23,7 @@ static std::string in_encoding_for(const Bridge& bridge, const Iid& iid)
#ifdef USE_DATABASE
const auto jid = bridge.get_bare_jid();
auto options = Database::get_irc_channel_options_with_server_default(jid, iid.get_server(), iid.get_local());
return options.encodingIn.value();
return options.encoding_in;
#else
(void)bridge;
(void)iid;
......@@ -38,7 +38,7 @@ Bridge::Bridge(std::string user_jid, BiboumiComponent& xmpp, std::shared_ptr<Pol
{
#ifdef USE_DATABASE
const auto options = Database::get_global_options(this->user_jid);
this->set_record_history(options.recordHistory.value());
this->set_record_history(options.record_history);
#endif
}
......@@ -258,7 +258,7 @@ void Bridge::send_channel_message(const Iid& iid, const std::string& body)
#ifdef USE_DATABASE
const auto xmpp_body = this->make_xmpp_body(line);
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());
#endif
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
const auto coptions = Database::get_irc_channel_options_with_server_default(this->user_jid,
iid.get_server(), iid.get_local());
persistent = coptions.persistent.value();
persistent = coptions.persistent;
#endif
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
const auto xmpp_body = this->make_xmpp_body(body, encoding);
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);
#endif
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
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.max_history_length);
chan_name.append(utils::empty_if_fixed_server("%" + hostname));
for (const auto& line: lines)
{
const auto seconds = line.date.value().timeStamp();
this->xmpp.send_history_message(chan_name, line.nick.value(), line.body.value(),
const auto seconds = line.date;
this->xmpp.send_history_message(chan_name, line.nick, line.body,
this->user_jid + "/" + resource, seconds);
}
#else
......
#include "biboumi.h"
#ifdef USE_DATABASE
#include <database/schema.hpp>
#include <database/old_schema.hpp>
#include <old_schema-odb.hpp>
#include <schema-odb.hpp>
#include <irc/iid.hpp>
#include <database/database.hpp>
#include <odb/database.hxx>
#include <odb/schema-catalog.hxx>
#include <odb/transaction.hxx>
#include <logger/logger.hpp>
#include <irc/iid.hpp>
#include <uuid/uuid.h>
......@@ -10,75 +23,103 @@
using namespace std::string_literals;
std::unique_ptr<db::BibouDB> Database::db;
std::unique_ptr<Database::Type> Database::db;
void Database::open(const std::string& filename, const std::string& db_type)
namespace
{
try
{
auto new_db = std::make_unique<db::BibouDB>(db_type,
"database="s + filename);
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;
template <typename From, typename To>
void migrate(Database::Type& db)
{
try {
odb::core::transaction t(db.begin());
auto res = db.template query<From>();
for (const auto& val: res)
{
To entry(val);
db.persist(entry);
}
t.commit();
} catch (...) {
// The table doesn’t exist, no data to migrate
return;
}
}
}
void Database::open(const std::string& filename)
{
Database::db = std::make_unique<Database::Type>(filename, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE);
if (db->schema_version() == 0)
{ // New schema does not exist at all. Create it and migrate old data if they exist
log_debug("Creating new database schema…");
{
odb::core::transaction t(db->begin());
odb::schema_catalog::create_schema(*db);
t.commit();
}
log_debug("Migrate data");
{
migrate<old_database::MucLogLine, db::MucLogLine>(*db);
migrate<old_database::GlobalOptions, db::GlobalOptions>(*db);
migrate<old_database::IrcServerOptions, db::IrcServerOptions>(*db);
migrate<old_database::IrcChannelOptions, db::IrcChannelOptions>(*db);
}
log_debug("Done.");
}
}
void Database::set_verbose(const bool val)
void Database::set_verbose(const bool)
{
Database::db->verbose = val;
}
db::GlobalOptions Database::get_global_options(const std::string& owner)
{
try {
auto options = litesql::select<db::GlobalOptions>(*Database::db,
db::GlobalOptions::Owner == owner).one();
return options;
} catch (const litesql::NotFound& e) {
db::GlobalOptions options(*Database::db);
options.owner = owner;
return options;
}
using Query = odb::query<db::GlobalOptions>;
Query query(Query::owner == owner);
db::GlobalOptions options;
options.owner = owner;
odb::core::transaction t(db->begin());
Database::db->query_one(query, options);
t.commit();
return options;
}
db::IrcServerOptions Database::get_irc_server_options(const std::string& owner,
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;
}
using Query = odb::query<db::IrcServerOptions>;
Query query(Query::owner == owner && Query::server == server);
db::IrcServerOptions options;
options.owner = owner;
options.server = server;
odb::core::transaction t(db->begin());
Database::db->query_one(query, options);
t.commit();
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;
}
using Query = odb::query<db::IrcChannelOptions>;
Query query(Query::owner == owner && Query::server == server && Query::channel == channel);
db::IrcChannelOptions options;
options.owner = owner;
options.server = server;
options.channel = channel;
odb::core::transaction t(db->begin());
Database::db->query_one(query, options);
t.commit();
return options;
}
db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(const std::string& owner,
......@@ -88,13 +129,13 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_default(cons
auto coptions = Database::get_irc_channel_options(owner, server, channel);
auto soptions = Database::get_irc_server_options(owner, server);
coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
soptions.encodingIn.value());
coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
soptions.encodingOut.value());
coptions.encoding_in = get_first_non_empty(coptions.encoding_in,
soptions.encoding_in);
coptions.encoding_out = get_first_non_empty(coptions.encoding_out,
soptions.encoding_out);
coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
soptions.maxHistoryLength.value());
coptions.max_history_length = get_first_non_empty(coptions.max_history_length,
soptions.max_history_length);
return coptions;
}
......@@ -107,65 +148,78 @@ db::IrcChannelOptions Database::get_irc_channel_options_with_server_and_global_d
auto soptions = Database::get_irc_server_options(owner, server);
auto goptions = Database::get_global_options(owner);
coptions.encodingIn = get_first_non_empty(coptions.encodingIn.value(),
soptions.encodingIn.value());
coptions.encodingOut = get_first_non_empty(coptions.encodingOut.value(),
soptions.encodingOut.value());
coptions.encoding_in = get_first_non_empty(coptions.encoding_in,
soptions.encoding_in);
coptions.encoding_out = get_first_non_empty(coptions.encoding_out,
soptions.encoding_out);
coptions.maxHistoryLength = get_first_non_empty(coptions.maxHistoryLength.value(),
soptions.maxHistoryLength.value(),
goptions.maxHistoryLength.value());
coptions.max_history_length = get_first_non_empty(coptions.max_history_length,
soptions.max_history_length,
goptions.max_history_length);
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& channel_name,
const std::string& server_name,
Database::time_point date,
const std::string& body,
const std::string& nick)
{
db::MucLogLine line(*Database::db);
db::MucLogLine line;
auto uuid = Database::gen_uuid();
line.uuid = uuid;
line.owner = owner;
line.ircChanName = iid.get_local();
line.ircServerName = iid.get_server();
line.channel_name = channel_name;
line.server_name = server_name;
line.date = std::chrono::duration_cast<std::chrono::seconds>(date.time_since_epoch()).count();
line.body = body;
line.nick = nick;
odb::core::transaction t(db->begin());
line.update();
Database::db->persist(line);
t.commit();
return uuid;
}
std::vector<db::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)
{
auto request = litesql::select<db::MucLogLine>(*Database::db,
db::MucLogLine::Owner == owner &&
db::MucLogLine::IrcChanName == chan_name &&
db::MucLogLine::IrcServerName == server);
request.orderBy(db::MucLogLine::Id, false);
if (limit >= 0)
request.limit(limit);
using Query = odb::query<db::MucLogLine>;
Query query{Query::owner == owner &&
Query::channel_name == chan_name &&
Query::server_name == server};
if (!start.empty())
{
const auto start_time = utils::parse_datetime(start);
if (start_time != -1)
request.where(db::MucLogLine::Date >= start_time);
query = query && (Query::date >= start_time);
}
if (!end.empty())
{
const auto end_time = utils::parse_datetime(end);
if (end_time != -1)
request.where(db::MucLogLine::Date <= end_time);
query = query && (Query::date <= end_time);
}
const auto& res = request.all();
return {res.crbegin(), res.crend()};
query += "ORDER BY" + Query::id + "DESC";
if (limit >= 0)
query += "LIMIT" + Query::_val(limit);
odb::core::transaction t(db->begin());
odb::result<db::MucLogLine> res = Database::db->query<db::MucLogLine>(query);
std::vector<db::MucLogLine> results{res.begin(), res.end()};
t.commit();
return {results.crbegin(), results.crend()};
}
void Database::close()
......
#pragma once
#include <biboumi.h>
#ifdef USE_DATABASE
#include "biboudb.hpp"
#include <memory>
#include <database/schema.hpp>
#include <odb/sqlite/database.hxx>
#include <schema-odb.hpp>
#include <litesql.hpp>
#include <chrono>
#include <iostream>
class Iid;
#include <memory>
class Database
{
public:
using Type = odb::sqlite::database;
using time_point = std::chrono::system_clock::time_point;
Database() = default;
~Database() = default;
......@@ -27,15 +26,28 @@ public:
static void set_verbose(const bool val);
template<typename PersistentType>
static size_t count()
template <typename T>
static auto persist(T&& object)
{
return litesql::select<PersistentType>(*Database::db).count();
odb::core::transaction t(Database::db->begin());
try {
Database::db->update(std::forward<T>(object));
} catch (const odb::object_not_persistent&) {
Database::db->persist(std::forward<T>(object));
}
t.commit();
}
/**
* Return the object from the db. Create it beforehand (with all default
* values) if it is not already present.
*/
template <typename T>
static std::size_t count()
{
odb::core::transaction t(Database::db->begin());
auto results = Database::db->query<T>();
auto size = std::distance(std::begin(results), std::end(results));
t.commit();
return size;
}
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);
......@@ -50,16 +62,16 @@ public:
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,
int limit=-1, const std::string& start="", const std::string& end="");
static std::string store_muc_message(const std::string& owner, const Iid& iid,
time_point date, const std::string& body, const std::string& nick);
static std::string store_muc_message(const std::string& owner, const std::string& channel_name, const std::string& server_name,
time_point date, const std::string& body, const std::string& nick);
static void close();
static void open(const std::string& filename, const std::string& db_type="sqlite3");
static void open(const std::string& filename);
private:
static std::string gen_uuid();
static std::unique_ptr<db::BibouDB> db;
static std::unique_ptr<Type> db;
};
#endif /* USE_DATABASE */
......
......@@ -156,13 +156,11 @@ IrcClient::IrcClient(std::shared_ptr<Poller>& poller, std::string hostname,
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
std::vector<std::string> ports = utils::split(options.ports, ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, false);
for (auto it = options.ports.rbegin(); it != options.ports.rend(); ++it)
this->ports_to_try.emplace(std::to_string(*it), false);
# ifdef BOTAN_FOUND
ports = utils::split(options.tlsPorts, ';', false);
for (auto it = ports.rbegin(); it != ports.rend(); ++it)
this->ports_to_try.emplace(*it, true);
for (auto it = options.tls_ports.rbegin(); it != options.tls_ports.rend(); ++it)
this->ports_to_try.emplace(std::to_string(*it), true);
# endif // BOTAN_FOUND
#else // not USE_DATABASE
......@@ -204,7 +202,7 @@ void IrcClient::start()
# ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
this->credential_manager.set_trusted_fingerprint(options.trustedFingerprint);
this->credential_manager.set_trusted_fingerprint(options.trusted_fingerprint);
# endif
#endif
this->connect(this->hostname, port, tls);
......@@ -275,8 +273,8 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
if (!options.pass.value().empty())
this->send_pass_command(options.pass.value());
if (!options.pass.empty())
this->send_pass_command(options.pass);
#endif
this->send_nick_command(this->current_nick);
......@@ -284,10 +282,10 @@ void IrcClient::on_connected()
#ifdef USE_DATABASE
if (Config::get("realname_customization", "true") == "true")
{
if (!options.username.value().empty())
this->username = options.username.value();
if (!options.realname.value().empty())
this->realname = options.realname.value();
if (!options.username.empty())
this->username = options.username;
if (!options.realname.empty())
this->realname = options.realname;
this->send_user_command(username, realname);
}
else
......@@ -894,8 +892,8 @@ void IrcClient::on_welcome_message(const IrcMessage& message)
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(),
this->get_hostname());
if (!options.afterConnectionCommand.value().empty())
this->send_raw(options.afterConnectionCommand.value());
if (!options.after_connection_command.empty())
this->send_raw(options.after_connection_command);
#endif
// Install a repeated events to regularly send a PING
TimedEventsManager::instance().add_event(TimedEvent(240s, std::bind(&IrcClient::send_ping_command, this),
......@@ -1260,7 +1258,7 @@ bool IrcClient::abort_on_invalid_cert() const
{
#ifdef USE_DATABASE
auto options = Database::get_irc_server_options(this->bridge.get_bare_jid(), this->hostname);
return options.verifyCert.value();
return options.verify_cert;
#endif
return true;
}
......
......@@ -26,7 +26,7 @@ void reload_process()
#ifdef USE_DATABASE
try {
open_database();
} catch (const litesql::DatabaseError&) {
} catch (...) {
log_warning("Re-using the previous database.");
}
#endif
......
......@@ -130,7 +130,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
{
XmlSubNode value(max_histo_length, "value");
value.set_inner(std::to_string(options.maxHistoryLength.value()));
value.set_inner(std::to_string(options.max_history_length));
}
XmlSubNode record_history(x, "field");
......@@ -142,7 +142,7 @@ void ConfigureGlobalStep1(XmppComponent&, AdhocSession& session, XmlNode& comman
{
XmlSubNode value(record_history, "value");
value.set_name("value");
if (options.recordHistory.value())
if (options.record_history)
value.set_inner("true");
else
value.set_inner("false");
......@@ -164,18 +164,18 @@ void ConfigureGlobalStep2(XmppComponent& xmpp_component, AdhocSession& session,
if (field->get_tag("var") == "max_history_length" &&
value && !value->get_inner().empty())
options.maxHistoryLength = value->get_inner();
options.max_history_length = atoi(value->get_inner().data());
else if (field->get_tag("var") == "record_history" &&
value && !value->get_inner().empty())
{
options.recordHistory = to_bool(value->get_inner());
options.record_history = to_bool(value->get_inner());
Bridge* bridge = biboumi_component.find_user_bridge(owner.bare());
if (bridge)
bridge->set_record_history(options.recordHistory.value());
bridge->set_record_history(options.record_history);
}
}
options.update();
Database::persist(options);
command_node.delete_all_children();
XmlSubNode note(command_node, "note");
......@@ -211,11 +211,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
ports["type"] = "text-multi";
ports["label"] = "Ports";
ports["desc"] = "List of ports to try, without TLS. Defaults: 6667.";
auto vals = utils::split(options.ports.value(), ';', false);
for (const auto& val: vals)
for (const auto& val: options.ports)
{
XmlSubNode ports_value(ports, "value");
ports_value.set_inner(val);
ports_value.set_inner(std::to_string(val));
}
#ifdef BOTAN_FOUND
......@@ -224,11 +223,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
tls_ports["type"] = "text-multi";
tls_ports["label"] = "TLS ports";
tls_ports["desc"] = "List of ports to try, with TLS. Defaults: 6697, 6670.";
vals = utils::split(options.tlsPorts.value(), ';', false);
for (const auto& val: vals)
for (const auto& val: options.tls_ports)
{
XmlSubNode tls_ports_value(tls_ports, "value");
tls_ports_value.set_inner(val);
tls_ports_value.set_inner(std::to_string(val));
}
XmlSubNode verify_cert(x, "field");
......@@ -237,7 +235,7 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
verify_cert["label"] = "Verify certificate";
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");
if (options.verifyCert.value())
if (options.verify_cert)
verify_cert_value.set_inner("true");
else
verify_cert_value.set_inner("false");
......@@ -246,10 +244,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
fingerprint["var"] = "fingerprint";
fingerprint["type"] = "text-single";
fingerprint["label"] = "SHA-1 fingerprint of the TLS certificate to trust.";
if (!options.trustedFingerprint.value().empty())
if (!options.trusted_fingerprint.empty())
{
XmlSubNode fingerprint_value(fingerprint, "value");
fingerprint_value.set_inner(options.trustedFingerprint.value());
fingerprint_value.set_inner(options.trusted_fingerprint);
}
#endif
......@@ -258,10 +256,10 @@ void ConfigureIrcServerStep1(XmppComponent&, AdhocSession& session, XmlNode& com
pass["type"] = "text-private";
pass["label"] = "Server password";
pass["desc"] = "Will be used in a PASS command when connecting";
if (!options.pass.value().empty())
if (!options.pass.empty())
{
XmlSubNode pass_value(pass, "value");
pass_value.set_inner(options.pass.value());
pass_value.set_inner(options.pass);