summaryrefslogtreecommitdiff
path: root/src/bridge
diff options
context:
space:
mode:
authorlouiz’ <louiz@louiz.org>2016-10-03 00:58:21 +0200
committerlouiz’ <louiz@louiz.org>2016-10-03 01:08:48 +0200
commit76a8189b46177eb78eee12d1cb3266f282acd380 (patch)
tree4db56bf37cd37c42c469e78bf6c10040ca8c014c /src/bridge
parentffad4306b9e9c6065a01a5fcaca668d70af0db8a (diff)
downloadbiboumi-76a8189b46177eb78eee12d1cb3266f282acd380.tar.gz
biboumi-76a8189b46177eb78eee12d1cb3266f282acd380.tar.bz2
biboumi-76a8189b46177eb78eee12d1cb3266f282acd380.tar.xz
biboumi-76a8189b46177eb78eee12d1cb3266f282acd380.zip
Implement result-set-management for LIST queries
ref #2948
Diffstat (limited to 'src/bridge')
-rw-r--r--src/bridge/bridge.cpp181
-rw-r--r--src/bridge/bridge.hpp26
-rw-r--r--src/bridge/list_element.hpp7
-rw-r--r--src/bridge/result_set_management.hpp10
4 files changed, 189 insertions, 35 deletions
diff --git a/src/bridge/bridge.cpp b/src/bridge/bridge.cpp
index d16875f..8849ef9 100644
--- a/src/bridge/bridge.cpp
+++ b/src/bridge/bridge.cpp
@@ -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();
+ // 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())))
+ {
+ 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;
- std::vector<ListElement> list;
+ 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;
+ };
- 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
+ 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)
{
- if (irc_hostname != iid.get_server())
+ 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;
- if (message.command == "263" || message.command == "RPL_TRYAGAIN" ||
- message.command == "ERR_TOOMANYMATCHES" || message.command == "ERR_NOSUCHSERVER")
+ };
+
+ 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)
{
- 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;
+ if (std::distance(begin, end) >= rs_info.max)
+ end = begin + rs_info.max;
}
- 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]);
- return false;
+ }
+ 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);
}
- 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;
+ 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;
}
- return false;
- };
- this->add_waiting_irc(std::move(cb));
+ 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 \ No newline at end of file
+#endif
diff --git a/src/bridge/bridge.hpp b/src/bridge/bridge.hpp
index b278ea7..1a1d201 100644
--- a/src/bridge/bridge.hpp
+++ b/src/bridge/bridge.hpp
@@ -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
diff --git a/src/bridge/list_element.hpp b/src/bridge/list_element.hpp
index 1eff2ee..554c83d 100644
--- a/src/bridge/list_element.hpp
+++ b/src/bridge/list_element.hpp
@@ -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{};
+};
diff --git a/src/bridge/result_set_management.hpp b/src/bridge/result_set_management.hpp
new file mode 100644
index 0000000..6ff82ba
--- /dev/null
+++ b/src/bridge/result_set_management.hpp
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <string>
+
+struct ResultSetInfo
+{
+ int max{-1};
+ std::string before{};
+ std::string after{};
+};