Implement result-set-management for LIST queries

ref #2948
This commit is contained in:
louiz’
2016-10-03 01:08:48 +02:00
parent ffad4306b9
commit 76a8189b46
8 changed files with 445 additions and 52 deletions
+154 -35
View File
@@ -1,5 +1,4 @@
#include <bridge/bridge.hpp>
#include <bridge/list_element.hpp>
#include <xmpp/biboumi_component.hpp>
#include <network/poller.hpp>
#include <utils/empty_if_fixed_server.hpp>
@@ -10,6 +9,7 @@
#include <utils/split.hpp>
#include <xmpp/jid.hpp>
#include <database/database.hpp>
#include "result_set_management.hpp"
using namespace std::string_literals;
@@ -386,45 +386,164 @@ void Bridge::send_irc_nick_change(const Iid& iid, const std::string& new_nick)
irc->send_nick_command(new_nick);
}
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id,
const std::string& to_jid)
void Bridge::send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
ResultSetInfo rs_info)
{
IrcClient* irc = this->get_irc_client(iid.get_server());
auto& list = channel_list_cache[iid.get_server()];
irc->send_list_command();
std::vector<ListElement> list;
irc_responder_callback_t cb = [this, iid, iq_id, to_jid, list=std::move(list)](const std::string& irc_hostname,
const IrcMessage& message) mutable -> bool
// We fetch the list from the IRC server only if we have a complete
// cached list that needs to be invalidated (that is, when the request
// doesn’t have a after or before, or when the list is empty).
// If the list is not complete, this means that a request is already
// ongoing, so we just need to add the callback.
// By default the list is complete and empty.
if (list.complete &&
(list.channels.empty() || (rs_info.after.empty() && rs_info.before.empty())))
{
if (irc_hostname != iid.get_server())
return false;
if (message.command == "263" || message.command == "RPL_TRYAGAIN" ||
message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER")
{
std::string text;
if (message.arguments.size() >= 2)
text = message.arguments[1];
this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id,
"wait", "service-unavailable", text, false);
return true;
}
else if (message.command == "322" || message.command == "RPL_LIST")
{ // Add element to list
if (message.arguments.size() == 4)
list.emplace_back(message.arguments[1], message.arguments[2],
message.arguments[3]);
IrcClient* irc = this->get_irc_client(iid.get_server());
irc->send_list_command();
// Add a callback that will populate our list
list.channels.clear();
list.complete = false;
irc_responder_callback_t cb = [this, iid](const std::string& irc_hostname,
const IrcMessage& message) -> bool
{
if (irc_hostname != iid.get_server())
return false;
auto& list = channel_list_cache[iid.get_server()];
if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
|| message.command == "ERR_NOSUCHSERVER")
{
list.complete = true;
return true;
}
else if (message.command == "322" || message.command == "RPL_LIST")
{ // Add element to list
if (message.arguments.size() == 4)
{
list.channels.emplace_back(message.arguments[1] + utils::empty_if_fixed_server("%" + iid.get_server()),
message.arguments[2], message.arguments[3]);
}
return false;
}
else if (message.command == "323" || message.command == "RPL_LISTEND")
{ // Send the iq response with the content of the list
list.complete = true;
return true;
}
return false;
};
this->add_waiting_irc(std::move(cb));
}
// If the list is complete, we immediately send the answer.
// Otherwise, we install a callback, that will populate our list and send
// the answer when we can.
if (list.complete)
{
this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
}
else
{
// Add a callback to answer the request as soon as we can
irc_responder_callback_t cb = [this, iid, iq_id, to_jid,
rs_info=std::move(rs_info)](const std::string& irc_hostname,
const IrcMessage& message) -> bool
{
if (irc_hostname != iid.get_server())
return false;
if (message.command == "263" || message.command == "RPL_TRYAGAIN" || message.command == "ERR_TOOMANYMATCHES"
|| message.command == "ERR_NOSUCHSERVER")
{
std::string text;
if (message.arguments.size() >= 2)
text = message.arguments[1];
this->xmpp.send_stanza_error("iq", to_jid, std::to_string(iid), iq_id, "wait", "service-unavailable", text, false);
return true;
}
else if (message.command == "322" || message.command == "RPL_LIST")
{
auto& list = channel_list_cache[iid.get_server()];
const auto res = this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
log_debug("We added a new channel in our list, can we send the result? ", std::boolalpha, res);
return res;
}
else if (message.command == "323" || message.command == "RPL_LISTEND")
{ // Send the iq response with the content of the list
auto& list = channel_list_cache[iid.get_server()];
this->send_matching_channel_list(list, rs_info, iq_id, to_jid, std::to_string(iid));
return true;
}
return false;
};
this->add_waiting_irc(std::move(cb));
}
}
bool Bridge::send_matching_channel_list(const ChannelList& channel_list, const ResultSetInfo& rs_info,
const std::string& id, const std::string& to_jid, const std::string& from)
{
auto begin = channel_list.channels.begin();
auto end = channel_list.channels.begin();
if (channel_list.complete)
{
begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
{
return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
});
if (begin == channel_list.channels.end())
begin = channel_list.channels.begin();
else
begin = std::next(begin);
end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
{
return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
});
if (rs_info.max >= 0)
{
if (std::distance(begin, end) >= rs_info.max)
end = begin + rs_info.max;
}
else if (message.command == "323" || message.command == "RPL_LISTEND")
{ // Send the iq response with the content of the list
this->xmpp.send_iq_room_list_result(iq_id, to_jid, std::to_string(iid), list);
return true;
}
else
{
if (rs_info.after.empty() && rs_info.before.empty() && rs_info.max < 0)
return false;
if (!rs_info.after.empty())
{
begin = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
{
return rs_info.after == element.channel + "@" + this->xmpp.get_served_hostname();
});
if (begin == channel_list.channels.end())
return false;
begin = std::next(begin);
}
return false;
};
this->add_waiting_irc(std::move(cb));
if (!rs_info.before.empty())
{
end = std::find_if(channel_list.channels.begin(), channel_list.channels.end(), [this, &rs_info](const ListElement& element)
{
return rs_info.before == element.channel + "@" + this->xmpp.get_served_hostname();
});
if (end == channel_list.channels.end())
return false;
}
if (rs_info.max >= 0)
{
if (std::distance(begin, end) < rs_info.max)
return false;
else
end = begin + rs_info.max;
}
}
this->xmpp.send_iq_room_list_result(id, to_jid, from, channel_list, begin, end, rs_info);
return true;
}
void Bridge::send_irc_kick(const Iid& iid, const std::string& target, const std::string& reason,
@@ -1002,4 +1121,4 @@ void Bridge::set_record_history(const bool val)
{
this->record_history = val;
}
#endif
#endif
+23 -3
View File
@@ -1,5 +1,7 @@
#pragma once
#include <bridge/result_set_management.hpp>
#include <bridge/list_element.hpp>
#include <irc/irc_message.hpp>
#include <irc/irc_client.hpp>
@@ -17,6 +19,7 @@
class BiboumiComponent;
class Poller;
class ResultSetInfo;
/**
* A callback called for each IrcMessage we receive. If the message triggers
@@ -87,8 +90,19 @@ public:
void send_irc_version_request(const std::string& irc_hostname, const std::string& target,
const std::string& iq_id, const std::string& to_jid,
const std::string& from_jid);
void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id,
const std::string& to_jid);
void send_irc_channel_list_request(const Iid& iid, const std::string& iq_id, const std::string& to_jid,
ResultSetInfo rs_info);
/**
* Check if the channel list contains what is needed to answer the RSM request,
* if it does, send the iq result. If the list is complete but does not contain
* everything, send the result anyway (because there are no more available
* channels that could complete the list).
*
* Returns true if we sent the answer.
*/
bool send_matching_channel_list(const ChannelList& channel_list,
const ResultSetInfo& rs_info, const std::string& id, const std::string& to_jid,
const std::string& from);
void forward_affiliation_role_change(const Iid& iid, const std::string& nick,
const std::string& affiliation, const std::string& role);
/**
@@ -271,7 +285,6 @@ private:
* response iq.
*/
std::vector<irc_responder_callback_t> waiting_irc;
/**
* Resources to IRC channel/server mapping:
*/
@@ -300,6 +313,13 @@ private:
* TODO: send message history
*/
void generate_channel_join_for_resource(const Iid& iid, const std::string& resource);
/**
* A cache of the channels list (as returned by the server on a LIST
* request), to be re-used on a subsequent XMPP list request that
* uses result-set-management.
*/
std::map<IrcHostname, ChannelList> channel_list_cache;
#ifdef USE_DATABASE
bool record_history { true };
#endif
+6 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include <vector>
#include <string>
struct ListElement
@@ -17,3 +17,8 @@ struct ListElement
};
struct ChannelList
{
bool complete{true};
std::vector<ListElement> channels{};
};
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <string>
struct ResultSetInfo
{
int max{-1};
std::string before{};
std::string after{};
};