From 1a3cdccd4c4465bdb684a4e0927c8431e306c7cc Mon Sep 17 00:00:00 2001 From: Shane Synan Date: Mon, 28 May 2018 21:17:10 -0500 Subject: [PATCH] common: Sort user prefix channelmodes on add/set Sort prefix channelmodes whenever adding/removing/setting per-channel IrcUser modes. This keeps the highest mode at the front, necessary for backlog storage to allow only showing the highest mode. It's not possible to get away without sorting as modes can be added in any order. modesToPrefixes/prefixesToModes operate in order, so sorting prefixes results in sorted modes for free. IrcUser modes don't need sorted as they're not hierarchical and not stored anywhere. --- src/common/ircchannel.cpp | 23 +++++++++++++++-------- src/common/network.cpp | 38 ++++++++++++++++++++++++++++++++++++++ src/common/network.h | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/common/ircchannel.cpp b/src/common/ircchannel.cpp index 4b8cd334..43e1dee1 100644 --- a/src/common/ircchannel.cpp +++ b/src/common/ircchannel.cpp @@ -170,6 +170,9 @@ void IrcChannel::joinIrcUsers(const QList &users, const QStringList & return; } + // Sort user modes first + const QStringList sortedModes = network()->sortPrefixModes(modes); + QStringList newNicks; QStringList newModes; QList newUsers; @@ -178,19 +181,19 @@ void IrcChannel::joinIrcUsers(const QList &users, const QStringList & for (int i = 0; i < users.count(); i++) { ircuser = users[i]; if (!ircuser || _userModes.contains(ircuser)) { - if (modes[i].count() > 1) { + if (sortedModes[i].count() > 1) { // Multiple modes received, do it one at a time // TODO Better way of syncing this without breaking protocol? - for (int i_m = 0; i_m < modes[i].count(); ++i_m) { - addUserMode(ircuser, modes[i][i_m]); + for (int i_m = 0; i_m < sortedModes[i].count(); ++i_m) { + addUserMode(ircuser, sortedModes[i][i_m]); } } else { - addUserMode(ircuser, modes[i]); + addUserMode(ircuser, sortedModes[i]); } continue; } - _userModes[ircuser] = modes[i]; + _userModes[ircuser] = sortedModes[i]; ircuser->joinChannel(this, true); connect(ircuser, SIGNAL(nickSet(QString)), this, SLOT(ircUserNickSet(QString))); @@ -199,7 +202,7 @@ void IrcChannel::joinIrcUsers(const QList &users, const QStringList & // the joins are propagated by the ircuser. The signal ircUserJoined is only for convenience newNicks << ircuser->nick(); - newModes << modes[i]; + newModes << sortedModes[i]; newUsers << ircuser; } @@ -266,7 +269,8 @@ void IrcChannel::part(const QString &nick) void IrcChannel::setUserModes(IrcUser *ircuser, const QString &modes) { if (isKnownUser(ircuser)) { - _userModes[ircuser] = modes; + // Keep user modes sorted + _userModes[ircuser] = network()->sortPrefixModes(modes); QString nick = ircuser->nick(); SYNC_OTHER(setUserModes, ARG(nick), ARG(modes)) emit ircUserModesSet(ircuser, modes); @@ -287,7 +291,8 @@ void IrcChannel::addUserMode(IrcUser *ircuser, const QString &mode) return; if (!_userModes[ircuser].contains(mode)) { - _userModes[ircuser] += mode; + // Keep user modes sorted + _userModes[ircuser] = network()->sortPrefixModes(_userModes[ircuser] + mode); QString nick = ircuser->nick(); SYNC_OTHER(addUserMode, ARG(nick), ARG(mode)) emit ircUserModeAdded(ircuser, mode); @@ -308,6 +313,7 @@ void IrcChannel::removeUserMode(IrcUser *ircuser, const QString &mode) return; if (_userModes[ircuser].contains(mode)) { + // Removing modes shouldn't mess up ordering _userModes[ircuser].remove(mode); QString nick = ircuser->nick(); SYNC_OTHER(removeUserMode, ARG(nick), ARG(mode)); @@ -345,6 +351,7 @@ void IrcChannel::initSetUserModes(const QVariantMap &usermodes) modes << iter.value().toString(); ++iter; } + // joinIrcUsers handles sorting modes joinIrcUsers(users, modes); } diff --git a/src/common/network.cpp b/src/common/network.cpp index 8e2eed44..dcca4f49 100644 --- a/src/common/network.cpp +++ b/src/common/network.cpp @@ -18,6 +18,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ +#include + #include #include "network.h" @@ -177,6 +179,42 @@ QString Network::modeToPrefix(const QString &mode) const } +QString Network::sortPrefixModes(const QString &modes) const +{ + // If modes is empty or we don't have any modes, nothing can be sorted, bail out early + if (modes.isEmpty() || prefixModes().isEmpty()) { + return modes; + } + + // Store a copy of the modes for modification + // QString should be efficient and not copy memory if nothing changes, but if mistaken, + // std::is_sorted could be called first. + QString sortedModes = QString(modes); + + // Sort modes as if a QChar array + // See https://en.cppreference.com/w/cpp/algorithm/sort + // Defining lambda with [&] implicitly captures variables by reference + std::sort(sortedModes.begin(), sortedModes.end(), [&](const QChar &lmode, const QChar &rmode) { + // Compare characters according to prefix modes + // Return true if lmode comes before rmode (is "less than") + + // Check for unknown modes... + if (!prefixModes().contains(lmode)) { + // Left mode not in prefix list, send to end + return false; + } else if (!prefixModes().contains(rmode)) { + // Right mode not in prefix list, send to end + return true; + } else { + // Both characters known, sort according to index in prefixModes() + return (prefixModes().indexOf(lmode) < prefixModes().indexOf(rmode)); + } + }); + + return sortedModes; +} + + QStringList Network::nicks() const { // we don't use _ircUsers.keys() since the keys may be diff --git a/src/common/network.h b/src/common/network.h index 37bdadbe..da295691 100644 --- a/src/common/network.h +++ b/src/common/network.h @@ -204,6 +204,41 @@ public : } /**@}*/ + /** + * Sorts the user channelmodes according to priority set by PREFIX + * + * Given a list of channel modes, sorts according to the order of PREFIX, putting the highest + * modes first. Any unknown modes are moved to the end in no given order. + * + * If prefix modes cannot be determined from the network, no changes will be made. + * + * @param modes User channelmodes + * @return Priority-sorted user channelmodes + */ + QString sortPrefixModes(const QString &modes) const; + + /**@{*/ + /** + * Sorts the list of users' channelmodes according to priority set by PREFIX + * + * Maintains order of the modes list. + * + * @seealso Network::sortPrefixModes() + * + * @param modesList List of users' channel modes + * @return Priority-sorted list of users' channel modes + */ + inline QStringList sortPrefixModes(const QStringList &modesList) const { + QStringList sortedModesList; + // Sort each individual mode string, appending back + // Must maintain the order received! + for (QString modes : modesList) { + sortedModesList << sortPrefixModes(modes); + } + return sortedModesList; + } + /**@}*/ + ChannelModeType channelModeType(const QString &mode); inline ChannelModeType channelModeType(const QCharRef &mode) { return channelModeType(QString(mode)); } -- 2.20.1