+154
-35
@@ -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
@@ -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
|
||||
|
||||
@@ -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{};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
struct ResultSetInfo
|
||||
{
|
||||
int max{-1};
|
||||
std::string before{};
|
||||
std::string after{};
|
||||
};
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <stdexcept>
|
||||
#include <iostream>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <louloulibs.h>
|
||||
#include <biboumi.h>
|
||||
@@ -27,6 +27,7 @@
|
||||
#endif
|
||||
|
||||
#include <database/database.hpp>
|
||||
#include <bridge/result_set_management.hpp>
|
||||
|
||||
using namespace std::string_literals;
|
||||
|
||||
@@ -463,7 +464,22 @@ void BiboumiComponent::handle_iq(const Stanza& stanza)
|
||||
}
|
||||
else if (node.empty() && iid.type == Iid::Type::Server)
|
||||
{ // Disco on an IRC server: get the list of channels
|
||||
bridge->send_irc_channel_list_request(iid, id, from);
|
||||
ResultSetInfo rs_info;
|
||||
const XmlNode* set_node = query->get_child("set", RSM_NS);
|
||||
if (set_node)
|
||||
{
|
||||
const XmlNode* after = set_node->get_child("after", RSM_NS);
|
||||
if (after)
|
||||
rs_info.after = after->get_inner();
|
||||
const XmlNode* before = set_node->get_child("before", RSM_NS);
|
||||
if (before)
|
||||
rs_info.before = before->get_inner();
|
||||
const XmlNode* max = set_node->get_child("max", RSM_NS);
|
||||
if (max)
|
||||
rs_info.max = std::atoi(max->get_inner().data());
|
||||
|
||||
}
|
||||
bridge->send_irc_channel_list_request(iid, id, from, std::move(rs_info));
|
||||
stanza_error.disable();
|
||||
}
|
||||
}
|
||||
@@ -749,10 +765,11 @@ void BiboumiComponent::send_ping_request(const std::string& from,
|
||||
this->waiting_iq[id] = result_cb;
|
||||
}
|
||||
|
||||
void BiboumiComponent::send_iq_room_list_result(const std::string& id,
|
||||
const std::string& to_jid,
|
||||
const std::string& from,
|
||||
const std::vector<ListElement>& rooms_list)
|
||||
void BiboumiComponent::send_iq_room_list_result(const std::string& id, const std::string& to_jid,
|
||||
const std::string& from, const ChannelList& channel_list,
|
||||
std::vector<ListElement>::const_iterator begin,
|
||||
std::vector<ListElement>::const_iterator end,
|
||||
const ResultSetInfo& rs_info)
|
||||
{
|
||||
Stanza iq("iq");
|
||||
iq["from"] = from + "@" + this->served_hostname;
|
||||
@@ -761,12 +778,41 @@ void BiboumiComponent::send_iq_room_list_result(const std::string& id,
|
||||
iq["type"] = "result";
|
||||
XmlNode query("query");
|
||||
query["xmlns"] = DISCO_ITEMS_NS;
|
||||
for (const auto& room: rooms_list)
|
||||
|
||||
for (auto it = begin; it != end; ++it)
|
||||
{
|
||||
XmlNode item("item");
|
||||
item["jid"] = room.channel + "%" + from + "@" + this->served_hostname;
|
||||
item["jid"] = it->channel + "@" + this->served_hostname;
|
||||
query.add_child(std::move(item));
|
||||
}
|
||||
|
||||
if ((rs_info.max >= 0 || !rs_info.after.empty() || !rs_info.before.empty()))
|
||||
{
|
||||
XmlNode set_node("set");
|
||||
set_node["xmlns"] = RSM_NS;
|
||||
|
||||
if (begin != channel_list.channels.cend())
|
||||
{
|
||||
XmlNode first_node("first");
|
||||
first_node["index"] = std::to_string(std::distance(channel_list.channels.cbegin(), begin));
|
||||
first_node.set_inner(begin->channel + "@" + this->served_hostname);
|
||||
set_node.add_child(std::move(first_node));
|
||||
}
|
||||
if (end != channel_list.channels.cbegin())
|
||||
{
|
||||
XmlNode last_node("last");
|
||||
last_node.set_inner(std::prev(end)->channel + "@" + this->served_hostname);
|
||||
set_node.add_child(std::move(last_node));
|
||||
}
|
||||
if (channel_list.complete)
|
||||
{
|
||||
XmlNode count_node("count");
|
||||
count_node.set_inner(std::to_string(channel_list.channels.size()));
|
||||
set_node.add_child(std::move(count_node));
|
||||
}
|
||||
query.add_child(std::move(set_node));
|
||||
}
|
||||
|
||||
iq.add_child(std::move(query));
|
||||
this->send_stanza(iq);
|
||||
}
|
||||
|
||||
@@ -79,9 +79,9 @@ public:
|
||||
/**
|
||||
* Send the channels list in one big stanza
|
||||
*/
|
||||
void send_iq_room_list_result(const std::string& id, const std::string& to_jid,
|
||||
const std::string& from,
|
||||
const std::vector<ListElement>& rooms_list);
|
||||
void send_iq_room_list_result(const std::string& id, const std::string& to_jid, const std::string& from,
|
||||
const ChannelList& channel_list, std::vector<ListElement>::const_iterator begin,
|
||||
std::vector<ListElement>::const_iterator end, const ResultSetInfo& rs_info);
|
||||
void send_invitation(const std::string& room_target, const std::string& jid_to, const std::string& author_nick);
|
||||
/**
|
||||
* Handle the various stanza types
|
||||
|
||||
Reference in New Issue
Block a user