client: Fix an issue with redirection in MessageFilter
[quassel.git] / src / client / messagefilter.cpp
index 6a05e9e..b4082e6 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-09 by the Quassel Project                          *
+ *   Copyright (C) 2005-2020 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
  *   You should have received a copy of the GNU General Public License     *
  *   along with this program; if not, write to the                         *
  *   Free Software Foundation, Inc.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
 #include "messagefilter.h"
 
+#include <algorithm>
+
+#include "buffermodel.h"
 #include "buffersettings.h"
 #include "client.h"
-#include "buffermodel.h"
+#include "clientignorelistmanager.h"
 #include "messagemodel.h"
 #include "networkmodel.h"
+#include "util.h"
 
-MessageFilter::MessageFilter(QAbstractItemModel *source, QObject *parent)
-  : QSortFilterProxyModel(parent),
-    _messageTypeFilter(0)
+MessageFilter::MessageFilter(QAbstractItemModel* source, QObject* parent)
+    : QSortFilterProxyModel(parent)
+    _messageTypeFilter(0)
 {
-  init();
-  setSourceModel(source);
+    init();
+    setSourceModel(source);
 }
 
-MessageFilter::MessageFilter(MessageModel *source, const QList<BufferId> &buffers, QObject *parent)
-  : QSortFilterProxyModel(parent),
-    _validBuffers(buffers.toSet()),
-    _messageTypeFilter(0)
+MessageFilter::MessageFilter(MessageModel* source, const QList<BufferId>& buffers, QObject* parent)
+    : QSortFilterProxyModel(parent)
+    , _validBuffers(toQSet(buffers))
+    _messageTypeFilter(0)
 {
-  init();
-  setSourceModel(source);
+    init();
+    setSourceModel(source);
 }
 
-void MessageFilter::init() {
-  setDynamicSortFilter(true);
-
-  BufferSettings defaultSettings;
-  defaultSettings.notify("UserNoticesInDefaultBuffer", this, SLOT(messageRedirectionChanged()));
-  defaultSettings.notify("UserNoticesInStatusBuffer", this, SLOT(messageRedirectionChanged()));
-  defaultSettings.notify("UserNoticesInCurrentBuffer", this, SLOT(messageRedirectionChanged()));
+void MessageFilter::init()
+{
+    setDynamicSortFilter(true);
 
-  defaultSettings.notify("serverNoticesInDefaultBuffer", this, SLOT(messageRedirectionChanged()));
-  defaultSettings.notify("serverNoticesInStatusBuffer", this, SLOT(messageRedirectionChanged()));
-  defaultSettings.notify("serverNoticesInCurrentBuffer", this, SLOT(messageRedirectionChanged()));
+    _userNoticesTarget = _serverNoticesTarget = _errorMsgsTarget = -1;
 
-  defaultSettings.notify("ErrorMsgsInDefaultBuffer", this, SLOT(messageRedirectionChanged()));
-  defaultSettings.notify("ErrorMsgsInStatusBuffer", this, SLOT(messageRedirectionChanged()));
-  defaultSettings.notify("ErrorMsgsInCurrentBuffer", this, SLOT(messageRedirectionChanged()));
-  messageRedirectionChanged();
+    BufferSettings defaultSettings;
+    defaultSettings.notify("UserNoticesTarget", this, &MessageFilter::messageRedirectionChanged);
+    defaultSettings.notify("ServerNoticesTarget", this, &MessageFilter::messageRedirectionChanged);
+    defaultSettings.notify("ErrorMsgsTarget", this, &MessageFilter::messageRedirectionChanged);
+    messageRedirectionChanged();
 
-  _messageTypeFilter = defaultSettings.messageFilter();
-  defaultSettings.notify("MessageTypeFilter", this, SLOT(messageTypeFilterChanged()));
+    _messageTypeFilter = defaultSettings.messageFilter();
+    defaultSettings.notify("MessageTypeFilter", this, &MessageFilter::messageTypeFilterChanged);
 
-  BufferSettings mySettings(idString());
-  if(mySettings.hasFilter())
-    _messageTypeFilter = mySettings.messageFilter();
-  mySettings.notify("MessageTypeFilter", this, SLOT(messageTypeFilterChanged()));
-  mySettings.notify("hasMessageTypeFilter", this, SLOT(messageTypeFilterChanged()));
+    BufferSettings mySettings(MessageFilter::idString());
+    if (mySettings.hasFilter())
+        _messageTypeFilter = mySettings.messageFilter();
+    mySettings.notify("MessageTypeFilter", this, &MessageFilter::messageTypeFilterChanged);
+    mySettings.notify("hasMessageTypeFilter", this, &MessageFilter::messageTypeFilterChanged);
 }
 
-void MessageFilter::messageTypeFilterChanged() {
-  int newFilter;
-  BufferSettings defaultSettings;
-  newFilter = BufferSettings().messageFilter();
-
-  BufferSettings mySettings(idString());
-  if(mySettings.hasFilter())
-    newFilter = mySettings.messageFilter();
-
-  if(_messageTypeFilter != newFilter) {
-    _messageTypeFilter = newFilter;
-    _filteredQuitMsgs.clear();
-    invalidateFilter();
-  }
+void MessageFilter::messageTypeFilterChanged()
+{
+    int newFilter;
+    BufferSettings defaultSettings;
+    newFilter = BufferSettings().messageFilter();
+
+    BufferSettings mySettings(idString());
+    if (mySettings.hasFilter())
+        newFilter = mySettings.messageFilter();
+
+    if (_messageTypeFilter != newFilter) {
+        _messageTypeFilter = newFilter;
+        _filteredQuitMsgTime.clear();
+        invalidateFilter();
+    }
 }
 
-void MessageFilter::messageRedirectionChanged() {
-  BufferSettings bufferSettings;
-  _userNoticesInDefaultBuffer = bufferSettings.value("UserNoticesInDefaultBuffer", QVariant(true)).toBool();
-  _userNoticesInStatusBuffer = bufferSettings.value("UserNoticesInStatusBuffer", QVariant(false)).toBool();
-  _userNoticesInCurrentBuffer = bufferSettings.value("UserNoticesInCurrentBuffer", QVariant(false)).toBool();
+void MessageFilter::messageRedirectionChanged()
+{
+    BufferSettings bufferSettings;
+    bool changed = false;
 
-  _serverNoticesInDefaultBuffer = bufferSettings.value("ServerNoticesInDefaultBuffer", QVariant(false)).toBool();
-  _serverNoticesInStatusBuffer = bufferSettings.value("ServerNoticesInStatusBuffer", QVariant(true)).toBool();
-  _serverNoticesInCurrentBuffer = bufferSettings.value("ServerNoticesInCurrentBuffer", QVariant(false)).toBool();
+    if (_userNoticesTarget != bufferSettings.userNoticesTarget()) {
+        _userNoticesTarget = bufferSettings.userNoticesTarget();
+        changed = true;
+    }
 
-  _errorMsgsInDefaultBuffer = bufferSettings.value("ErrorMsgsInDefaultBuffer", QVariant(true)).toBool();
-  _errorMsgsInStatusBuffer = bufferSettings.value("ErrorMsgsInStatusBuffer", QVariant(false)).toBool();
-  _errorMsgsInCurrentBuffer = bufferSettings.value("ErrorMsgsInCurrentBuffer", QVariant(false)).toBool();
+    if (_serverNoticesTarget != bufferSettings.serverNoticesTarget()) {
+        _serverNoticesTarget = bufferSettings.serverNoticesTarget();
+        changed = true;
+    }
+
+    if (_errorMsgsTarget != bufferSettings.errorMsgsTarget()) {
+        _errorMsgsTarget = bufferSettings.errorMsgsTarget();
+        changed = true;
+    }
 
-  invalidateFilter();
+    if (changed)
+        invalidateFilter();
 }
 
-QString MessageFilter::idString() const {
-  if(_validBuffers.isEmpty())
-    return "*";
+QString MessageFilter::idString() const
+{
+    if (_validBuffers.isEmpty())
+        return "*";
 
-  QList<BufferId> bufferIds = _validBuffers.toList();
-  qSort(bufferIds);
+    QList<BufferId> bufferIds = _validBuffers.values();
+    std::sort(bufferIds.begin(), bufferIds.end());
 
-  QStringList bufferIdStrings;
-  foreach(BufferId id, bufferIds)
-    bufferIdStrings << QString::number(id.toInt());
+    QStringList bufferIdStrings;
+    foreach (BufferId id, bufferIds)
+        bufferIdStrings << QString::number(id.toInt());
 
-  return bufferIdStrings.join("|");
+    return bufferIdStrings.join("|");
 }
 
-bool MessageFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const {
-  Q_UNUSED(sourceParent);
-  QModelIndex sourceIdx = sourceModel()->index(sourceRow, 2);
-  Message::Type messageType = (Message::Type)sourceModel()->data(sourceIdx, MessageModel::TypeRole).toInt();
-
-  // apply message type filter
-  if(_messageTypeFilter & messageType)
-    return false;
-
-  if(_validBuffers.isEmpty())
-    return true;
-
-  BufferId bufferId = sourceModel()->data(sourceIdx, MessageModel::BufferIdRole).value<BufferId>();
-  if(!bufferId.isValid()) {
-    return true;
-  }
-
-  MsgId msgId = sourceModel()->data(sourceIdx, MessageModel::MsgIdRole).value<MsgId>();
-  Message::Flags flags = (Message::Flags)sourceModel()->data(sourceIdx, MessageModel::FlagsRole).toInt();
-
-  NetworkId myNetworkId = networkId();
-  NetworkId msgNetworkId = Client::networkModel()->networkId(bufferId);
-  if(myNetworkId != msgNetworkId)
-    return false;
-
-  bool redirect = false;
-  bool inDefaultBuffer;
-  bool inStatusBuffer;
-  bool inCurrentBuffer;
-
-  switch(messageType) {
-  case Message::Notice:
-    if(Client::networkModel()->bufferType(bufferId) != BufferInfo::ChannelBuffer) {
-      redirect = true;
-      if(flags & Message::ServerMsg) {
-       // server notice
-       inDefaultBuffer = _serverNoticesInDefaultBuffer;
-       inStatusBuffer = _serverNoticesInStatusBuffer;
-       inCurrentBuffer = _serverNoticesInCurrentBuffer;
-      } else {
-       inDefaultBuffer = _userNoticesInDefaultBuffer;
-       inStatusBuffer = _userNoticesInStatusBuffer;
-       inCurrentBuffer = _userNoticesInCurrentBuffer;
-      }
-    }
-    break;
-  case Message::Error:
-    redirect = true;
-    inDefaultBuffer = _errorMsgsInDefaultBuffer;
-    inStatusBuffer = _errorMsgsInStatusBuffer;
-    inCurrentBuffer = _errorMsgsInCurrentBuffer;
-    break;
-  default:
-    break;
-  }
-
-  if(redirect) {
-    if(_redirectedMsgs.contains(msgId))
-      return true;
-
-    if(inDefaultBuffer && _validBuffers.contains(bufferId))
-      return true;
-
-    if(inCurrentBuffer && !(flags & Message::Backlog) && _validBuffers.contains(Client::bufferModel()->currentIndex().data(NetworkModel::BufferIdRole).value<BufferId>())) {
-      BufferId redirectedTo = sourceModel()->data(sourceIdx, MessageModel::RedirectedToRole).value<BufferId>();
-      if(!redirectedTo.isValid()) {
-       sourceModel()->setData(sourceIdx, QVariant::fromValue<BufferId>(singleBufferId()), MessageModel::RedirectedToRole);
-       _redirectedMsgs << msgId;
-       return true;
-      } else if(_validBuffers.contains(redirectedTo)) {
-       return true;
-      }
-    }
-
-    QSet<BufferId>::const_iterator idIter = _validBuffers.constBegin();
-    while(idIter != _validBuffers.constEnd()) {
-      if(inStatusBuffer && Client::networkModel()->bufferType(*idIter) == BufferInfo::StatusBuffer)
-       return true;
-      idIter++;
-    }
-
-    return false;
-  }
-
+bool MessageFilter::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const
+{
+    Q_UNUSED(sourceParent);
+    QModelIndex sourceIdx = sourceModel()->index(sourceRow, 2);
+    Message::Type messageType = (Message::Type)sourceIdx.data(MessageModel::TypeRole).toInt();
 
-  if(_validBuffers.contains(bufferId)) {
-    return true;
-  } else {
-    // show Quit messages in Query buffers:
-    if(bufferType() != BufferInfo::QueryBuffer)
-      return false;
-    if(!(messageType & Message::Quit))
-      return false;
+    // apply message type filter
+    if (_messageTypeFilter & messageType)
+        return false;
 
-    if(myNetworkId != msgNetworkId)
-      return false;
+    if (_validBuffers.isEmpty())
+        return true;
 
-    uint messageTimestamp = sourceModel()->data(sourceIdx, MessageModel::TimestampRole).value<QDateTime>().toTime_t();
-    QString quiter = sourceModel()->data(sourceIdx, Qt::DisplayRole).toString().section(' ', 0, 0, QString::SectionSkipEmpty).toLower();
-    if(quiter != bufferName().toLower())
-      return false;
+    BufferId bufferId = sourceIdx.data(MessageModel::BufferIdRole).value<BufferId>();
+    if (!bufferId.isValid()) {
+        return true;
+    }
 
-    if(_filteredQuitMsgs.contains(quiter, messageTimestamp))
-      return false;
+    // MsgId msgId = sourceIdx.data(MessageModel::MsgIdRole).value<MsgId>();
+    Message::Flags flags = (Message::Flags)sourceIdx.data(MessageModel::FlagsRole).toInt();
+
+    NetworkId myNetworkId = networkId();
+    NetworkId msgNetworkId = Client::networkModel()->networkId(bufferId);
+    if (myNetworkId != msgNetworkId)
+        return false;
+
+    // ignorelist handling
+    // only match if message is not flagged as server msg
+    if (!(flags & Message::ServerMsg) && Client::ignoreListManager()
+        && Client::ignoreListManager()->match(sourceIdx.data(MessageModel::MessageRole).value<Message>(),
+                                              Client::networkModel()->networkName(bufferId)))
+        return false;
+
+    if (flags & Message::Redirected) {
+        int redirectionTarget = 0;
+        switch (messageType) {
+        case Message::Notice:
+            if (Client::networkModel()->bufferType(bufferId) != BufferInfo::ChannelBuffer) {
+                if (flags & Message::ServerMsg) {
+                    // server notice
+                    redirectionTarget = _serverNoticesTarget;
+                }
+                else {
+                    redirectionTarget = _userNoticesTarget;
+                }
+            }
+            break;
+        case Message::Error:
+            redirectionTarget = _errorMsgsTarget;
+            break;
+        default:
+            break;
+        }
+
+        if (redirectionTarget & BufferSettings::DefaultBuffer && _validBuffers.contains(bufferId))
+            return true;
+
+        if (redirectionTarget & BufferSettings::CurrentBuffer && !(flags & Message::Backlog)) {
+            BufferId redirectedTo = sourceModel()->data(sourceIdx, MessageModel::RedirectedToRole).value<BufferId>();
+            if (!redirectedTo.isValid()) {
+                redirectedTo = Client::bufferModel()->currentIndex().data(NetworkModel::BufferIdRole).value<BufferId>();
+                if (redirectedTo.isValid())
+                    sourceModel()->setData(sourceIdx, QVariant::fromValue(redirectedTo), MessageModel::RedirectedToRole);
+            }
+
+            if (_validBuffers.contains(redirectedTo))
+                return true;
+        }
+
+        if (redirectionTarget & BufferSettings::StatusBuffer) {
+            QSet<BufferId>::const_iterator idIter = _validBuffers.constBegin();
+            while (idIter != _validBuffers.constEnd()) {
+                if (Client::networkModel()->bufferType(*idIter) == BufferInfo::StatusBuffer)
+                    return true;
+                ++idIter;
+            }
+        }
+
+        return false;
+    }
 
-    MessageFilter *that = const_cast<MessageFilter *>(this);
-    that->_filteredQuitMsgs.insert(quiter,  messageTimestamp);
-    return true;
-  }
+    if (_validBuffers.contains(bufferId)) {
+        return true;
+    }
+    else {
+        // show Quit messages in Query buffers:
+        if (bufferType() != BufferInfo::QueryBuffer)
+            return false;
+        if (!(messageType & Message::Quit))
+            return false;
+
+        if (myNetworkId != msgNetworkId)
+            return false;
+
+        // Extract timestamp and nickname from the new quit message
+        qint64 messageTimestamp = sourceModel()->data(sourceIdx, MessageModel::TimestampRole).value<QDateTime>().toMSecsSinceEpoch();
+        QString quiter = nickFromMask(sourceModel()->data(sourceIdx, MessageModel::MessageRole).value<Message>().sender()).toLower();
+
+        // Check that nickname matches query name
+        if (quiter != bufferName().toLower())
+            return false;
+
+        // Check if a quit message was already forwarded within +/- 1000 ms
+        static constexpr qint64 MAX_QUIT_DELTA_MS = 1 * 1000;
+        // No need to check if it's the appropriate buffer, each query has a unique message filter
+        if (std::binary_search(_filteredQuitMsgTime.begin(), _filteredQuitMsgTime.end(), messageTimestamp, [](qint64 a, qint64 b) {
+                return ((a + MAX_QUIT_DELTA_MS) < b);
+            })) {
+            // New element is less than if at least 1000 ms older/newer
+            // Match found, no need to forward another quit message
+            return false;
+        }
+
+        // Mark query as having a quit message inserted
+        auto* that = const_cast<MessageFilter*>(this);
+        that->_filteredQuitMsgTime.insert(messageTimestamp);
+        return true;
+    }
 }
 
-void MessageFilter::requestBacklog() {
-  QSet<BufferId>::const_iterator bufferIdIter = _validBuffers.constBegin();
-  while(bufferIdIter != _validBuffers.constEnd()) {
-    Client::messageModel()->requestBacklog(*bufferIdIter);
-    bufferIdIter++;
-  }
+void MessageFilter::requestBacklog()
+{
+    QSet<BufferId>::const_iterator bufferIdIter = _validBuffers.constBegin();
+    while (bufferIdIter != _validBuffers.constEnd()) {
+        Client::messageModel()->requestBacklog(*bufferIdIter);
+        ++bufferIdIter;
+    }
 }