summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/users.h26
-rw-r--r--src/modules/m_ircv3.cpp58
-rw-r--r--src/usermanager.cpp26
-rw-r--r--src/users.cpp121
4 files changed, 124 insertions, 107 deletions
diff --git a/include/users.h b/include/users.h
index ceee4396b..fa8f610bc 100644
--- a/include/users.h
+++ b/include/users.h
@@ -248,6 +248,19 @@ class CoreExport User : public Extensible
std::bitset<ModeParser::MODEID_MAX> modes;
public:
+ /** To execute a function for each local neighbor of a user, inherit from this class and
+ * pass an instance of it to User::ForEachNeighbor().
+ */
+ class ForEachNeighborHandler
+ {
+ public:
+ /** Method to execute for each local neighbor of a user.
+ * Derived classes must implement this.
+ * @param user Current neighbor
+ */
+ virtual void Execute(LocalUser* user) = 0;
+ };
+
/** List of Memberships for this user
*/
typedef insp::intrusive_list<Membership> ChanList;
@@ -535,12 +548,15 @@ class CoreExport User : public Extensible
*/
void WriteCommon(const char* text, ...) CUSTOM_PRINTF(2, 3);
- /** Write a quit message to all common users, as in User::WriteCommonExcept but with a specific
- * quit message for opers only.
- * @param normal_text Normal user quit message
- * @param oper_text Oper only quit message
+ /** Execute a function once for each local neighbor of this user. By default, the neighbors of a user are the users
+ * who have at least one common channel with the user. Modules are allowed to alter the set of neighbors freely.
+ * This function is used for example to send something conditionally to neighbors, or to send different messages
+ * to different users depending on their oper status.
+ * @param handler Function object to call, inherited from ForEachNeighborHandler.
+ * @param include_self True to include this user in the set of neighbors, false otherwise.
+ * Modules may override this. Has no effect if this user is not local.
*/
- void WriteCommonQuit(const std::string &normal_text, const std::string &oper_text);
+ void ForEachNeighbor(ForEachNeighborHandler& handler, bool include_self = true);
/** Dump text to a user target, splitting it appropriately to fit
* @param linePrefix text to prefix each complete line with
diff --git a/src/modules/m_ircv3.cpp b/src/modules/m_ircv3.cpp
index 4eb54d2a6..b1c04cdf5 100644
--- a/src/modules/m_ircv3.cpp
+++ b/src/modules/m_ircv3.cpp
@@ -20,6 +20,26 @@
#include "modules/account.h"
#include "modules/cap.h"
+class WriteNeighboursWithExt : public User::ForEachNeighborHandler
+{
+ const LocalIntExt& ext;
+ const std::string& msg;
+
+ void Execute(LocalUser* user) CXX11_OVERRIDE
+ {
+ if (ext.get(user))
+ user->Write(msg);
+ }
+
+ public:
+ WriteNeighboursWithExt(User* user, const std::string& message, const LocalIntExt& extension)
+ : ext(extension)
+ , msg(message)
+ {
+ user->ForEachNeighbor(*this, false);
+ }
+};
+
class ModuleIRCv3 : public Module
{
GenericCap cap_accountnotify;
@@ -31,44 +51,6 @@ class ModuleIRCv3 : public Module
CUList last_excepts;
- void WriteNeighboursWithExt(User* user, const std::string& line, const LocalIntExt& ext)
- {
- IncludeChanList chans(user->chans.begin(), user->chans.end());
-
- std::map<User*, bool> exceptions;
- FOREACH_MOD(OnBuildNeighborList, (user, chans, exceptions));
-
- // Send it to all local users who were explicitly marked as neighbours by modules and have the required ext
- for (std::map<User*, bool>::const_iterator i = exceptions.begin(); i != exceptions.end(); ++i)
- {
- LocalUser* u = IS_LOCAL(i->first);
- if ((u) && (i->second) && (ext.get(u)))
- u->Write(line);
- }
-
- // Now consider sending it to all other users who has at least a common channel with the user
- std::set<User*> already_sent;
- for (IncludeChanList::const_iterator i = chans.begin(); i != chans.end(); ++i)
- {
- const Channel::MemberMap& userlist = (*i)->chan->GetUsers();
- for (Channel::MemberMap::const_iterator m = userlist.begin(); m != userlist.end(); ++m)
- {
- /*
- * Send the line if the channel member in question meets all of the following criteria:
- * - local
- * - not the user who is doing the action (i.e. whose channels we're iterating)
- * - has the given extension
- * - not on the except list built by modules
- * - we haven't sent the line to the member yet
- *
- */
- LocalUser* member = IS_LOCAL(m->first);
- if ((member) && (member != user) && (ext.get(member)) && (exceptions.find(member) == exceptions.end()) && (already_sent.insert(member).second))
- member->Write(line);
- }
- }
- }
-
public:
ModuleIRCv3() : cap_accountnotify(this, "account-notify"),
cap_awaynotify(this, "away-notify"),
diff --git a/src/usermanager.cpp b/src/usermanager.cpp
index 52cb4989f..5d07c4d79 100644
--- a/src/usermanager.cpp
+++ b/src/usermanager.cpp
@@ -24,6 +24,30 @@
#include "xline.h"
#include "iohook.h"
+namespace
+{
+ class WriteCommonQuit : public User::ForEachNeighborHandler
+ {
+ std::string line;
+ std::string operline;
+
+ void Execute(LocalUser* user) CXX11_OVERRIDE
+ {
+ user->Write(user->IsOper() ? operline : line);
+ }
+
+ public:
+ WriteCommonQuit(User* user, const std::string& msg, const std::string& opermsg)
+ : line(":" + user->GetFullHost() + " QUIT :")
+ , operline(line)
+ {
+ line += msg;
+ operline += opermsg;
+ user->ForEachNeighbor(*this, false);
+ }
+ };
+}
+
UserManager::UserManager()
: unregistered_count(0)
{
@@ -180,7 +204,7 @@ void UserManager::QuitUser(User* user, const std::string& quitreason, const std:
if (user->registered == REG_ALL)
{
FOREACH_MOD(OnUserQuit, (user, reason, *operreason));
- user->WriteCommonQuit(reason, *operreason);
+ WriteCommonQuit(user, reason, *operreason);
}
else
unregistered_count--;
diff --git a/src/users.cpp b/src/users.cpp
index 34986a183..12243c64b 100644
--- a/src/users.cpp
+++ b/src/users.cpp
@@ -845,11 +845,27 @@ void User::WriteFrom(User *user, const char* text, ...)
this->WriteFrom(user, textbuffer);
}
-void User::WriteCommon(const char* text, ...)
+namespace
{
- if (this->registered != REG_ALL || quitting)
- return;
+ class WriteCommonRawHandler : public User::ForEachNeighborHandler
+ {
+ const std::string& msg;
+ void Execute(LocalUser* user) CXX11_OVERRIDE
+ {
+ user->Write(msg);
+ }
+
+ public:
+ WriteCommonRawHandler(const std::string& message)
+ : msg(message)
+ {
+ }
+ };
+}
+
+void User::WriteCommon(const char* text, ...)
+{
std::string textbuffer;
VAFORMAT(textbuffer, text, text);
textbuffer = ":" + this->GetFullHost() + " " + textbuffer;
@@ -858,79 +874,58 @@ void User::WriteCommon(const char* text, ...)
void User::WriteCommonRaw(const std::string &line, bool include_self)
{
- if (this->registered != REG_ALL || quitting)
- return;
-
- LocalUser::already_sent_id++;
-
- IncludeChanList include_c(chans.begin(), chans.end());
- std::map<User*,bool> exceptions;
-
- exceptions[this] = include_self;
-
- FOREACH_MOD(OnBuildNeighborList, (this, include_c, exceptions));
-
- for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
- {
- LocalUser* u = IS_LOCAL(i->first);
- if (u && !u->quitting)
- {
- u->already_sent = LocalUser::already_sent_id;
- if (i->second)
- u->Write(line);
- }
- }
- for (IncludeChanList::const_iterator v = include_c.begin(); v != include_c.end(); ++v)
- {
- Channel* c = (*v)->chan;
- const Channel::MemberMap& ulist = c->GetUsers();
- for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); ++i)
- {
- LocalUser* u = IS_LOCAL(i->first);
- if (u && u->already_sent != LocalUser::already_sent_id)
- {
- u->already_sent = LocalUser::already_sent_id;
- u->Write(line);
- }
- }
- }
+ WriteCommonRawHandler handler(line);
+ ForEachNeighbor(handler, include_self);
}
-void User::WriteCommonQuit(const std::string &normal_text, const std::string &oper_text)
+void User::ForEachNeighbor(ForEachNeighborHandler& handler, bool include_self)
{
- if (this->registered != REG_ALL)
- return;
+ // The basic logic for visiting the neighbors of a user is to iterate the channel list of the user
+ // and visit all users on those channels. Because two users may share more than one common channel,
+ // we must skip users that we have already visited.
+ // To do this, we make use of a global counter and an integral 'already_sent' field in LocalUser.
+ // The global counter is incremented every time we do something for each neighbor of a user. Then,
+ // before visiting a member we examine user->already_sent. If it's equal to the current counter, we
+ // skip the member. Otherwise, we set it to the current counter and visit the member.
- already_sent_t uniq_id = ++LocalUser::already_sent_id;
-
- const std::string normalMessage = ":" + this->GetFullHost() + " QUIT :" + normal_text;
- const std::string operMessage = ":" + this->GetFullHost() + " QUIT :" + oper_text;
-
- IncludeChanList include_c(chans.begin(), chans.end());
- std::map<User*,bool> exceptions;
+ // Ask modules to build a list of exceptions.
+ // Mods may also exclude entire channels by erasing them from include_chans.
+ IncludeChanList include_chans(chans.begin(), chans.end());
+ std::map<User*, bool> exceptions;
+ exceptions[this] = include_self;
+ FOREACH_MOD(OnBuildNeighborList, (this, include_chans, exceptions));
- FOREACH_MOD(OnBuildNeighborList, (this, include_c, exceptions));
+ // Get next id, guaranteed to differ from the already_sent field of all users
+ const already_sent_t newid = ++LocalUser::already_sent_id;
- for (std::map<User*,bool>::iterator i = exceptions.begin(); i != exceptions.end(); ++i)
+ // Handle exceptions first
+ for (std::map<User*, bool>::const_iterator i = exceptions.begin(); i != exceptions.end(); ++i)
{
- LocalUser* u = IS_LOCAL(i->first);
- if (u && !u->quitting)
+ LocalUser* curr = IS_LOCAL(i->first);
+ if (curr)
{
- u->already_sent = uniq_id;
- if (i->second)
- u->Write(u->IsOper() ? operMessage : normalMessage);
+ // Mark as visited to ensure we won't visit again if there is a common channel
+ curr->already_sent = newid;
+ // Always treat quitting users as excluded
+ if ((i->second) && (!curr->quitting))
+ handler.Execute(curr);
}
}
- for (IncludeChanList::const_iterator v = include_c.begin(); v != include_c.end(); ++v)
+
+ // Now consider the real neighbors
+ for (IncludeChanList::const_iterator i = include_chans.begin(); i != include_chans.end(); ++i)
{
- const Channel::MemberMap& ulist = (*v)->chan->GetUsers();
- for (Channel::MemberMap::const_iterator i = ulist.begin(); i != ulist.end(); i++)
+ Channel* chan = (*i)->chan;
+ const Channel::MemberMap& userlist = chan->GetUsers();
+ for (Channel::MemberMap::const_iterator j = userlist.begin(); j != userlist.end(); ++j)
{
- LocalUser* u = IS_LOCAL(i->first);
- if (u && (u->already_sent != uniq_id))
+ LocalUser* curr = IS_LOCAL(j->first);
+ // User not yet visited?
+ if ((curr) && (curr->already_sent != newid))
{
- u->already_sent = uniq_id;
- u->Write(u->IsOper() ? operMessage : normalMessage);
+ // Mark as visited and execute function
+ curr->already_sent = newid;
+ handler.Execute(curr);
}
}
}