common: Add auto-caching NickHighlightMatcher
authorShane Synan <digitalcircuit36939@gmail.com>
Sat, 1 Sep 2018 21:33:03 +0000 (16:33 -0500)
committerManuel Nickschas <sputnick@quassel-irc.org>
Mon, 3 Sep 2018 20:12:02 +0000 (22:12 +0200)
Add NickHighlightMatcher class to unify handling of nick highlight
matching in Quassel, including automatically updating the expression
matcher instance as needed per network.

Cached ExpressionMatch objects are updated on demand after any change
in nickname configuration or active/configured nicks.

This lays the foundation for performance and readibility improvements
in future commits.

src/common/CMakeLists.txt
src/common/nickhighlightmatcher.cpp [new file with mode: 0644]
src/common/nickhighlightmatcher.h [new file with mode: 0644]

index 4274cf4..6a22061 100644 (file)
@@ -32,6 +32,7 @@ set(SOURCES
     network.cpp
     networkconfig.cpp
     networkevent.cpp
+    nickhighlightmatcher.cpp
     peer.cpp
     peerfactory.cpp
     presetnetworks.cpp
diff --git a/src/common/nickhighlightmatcher.cpp b/src/common/nickhighlightmatcher.cpp
new file mode 100644 (file)
index 0000000..a945923
--- /dev/null
@@ -0,0 +1,89 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2018 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   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.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#include "nickhighlightmatcher.h"
+
+#include <QDebug>
+#include <QString>
+#include <QStringList>
+
+bool NickHighlightMatcher::match(const QString &string, const NetworkId &netId,
+                                 const QString &currentNick, const QStringList &identityNicks) const
+{
+    // Never match for no nicknames
+    if (_highlightMode == HighlightNickType::NoNick) {
+        return false;
+    }
+
+    // Don't match until current nickname is known
+    if (currentNick.isEmpty()) {
+        return false;
+    }
+
+    // Make sure expression matcher is ready
+    determineExpressions(netId, currentNick, identityNicks);
+
+    // Check for a match
+    if (_nickMatchCache[netId].matcher.isValid()
+            && _nickMatchCache[netId].matcher.match(string)) {
+        // Nick matcher is valid and match found
+        return true;
+    }
+
+    return false;
+}
+
+
+void NickHighlightMatcher::determineExpressions(const NetworkId &netId, const QString &currentNick,
+                                                const QStringList &identityNicks) const
+{
+    // Don't do anything for no nicknames
+    if (_highlightMode == HighlightNickType::NoNick) {
+        return;
+    }
+
+    // Only update if needed (check nickname config, current nick, identity nicks for change)
+    if (_nickMatchCache.contains(netId)
+            && _nickMatchCache[netId].nickCurrent == currentNick
+            && _nickMatchCache[netId].identityNicks == identityNicks) {
+        return;
+    }
+
+    // Add all nicknames
+    QStringList nickList;
+    if (_highlightMode == HighlightNickType::CurrentNick) {
+        nickList << currentNick;
+    }
+    else if (_highlightMode == HighlightNickType::AllNicks) {
+        nickList = identityNicks;
+        if (!nickList.contains(currentNick))
+            nickList.prepend(currentNick);
+    }
+
+    // Set up phrase matcher, joining with newlines
+    _nickMatchCache[netId].matcher =
+            ExpressionMatch(nickList.join("\n"), ExpressionMatch::MatchMode::MatchMultiPhrase,
+                            _isCaseSensitive);
+
+    _nickMatchCache[netId].nickCurrent = currentNick;
+    _nickMatchCache[netId].identityNicks = identityNicks;
+
+    qDebug() << "Regenerated nickname matching cache for network ID" << netId;
+}
diff --git a/src/common/nickhighlightmatcher.h b/src/common/nickhighlightmatcher.h
new file mode 100644 (file)
index 0000000..efc3d8e
--- /dev/null
@@ -0,0 +1,168 @@
+/***************************************************************************
+ *   Copyright (C) 2005-2018 by the Quassel Project                        *
+ *   devel@quassel-irc.org                                                 *
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) version 3.                                           *
+ *                                                                         *
+ *   This program is distributed in the hope that it will be useful,       *
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
+ *   GNU General Public License for more details.                          *
+ *                                                                         *
+ *   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.,                                       *
+ *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
+ ***************************************************************************/
+
+#pragma once
+
+#include <QHash>
+#include <QString>
+#include <QStringList>
+
+#include "expressionmatch.h"
+#include "types.h"
+
+/**
+ * Nickname matcher with automatic caching for performance
+ */
+class NickHighlightMatcher
+{
+public:
+    /// Nickname highlighting mode
+#if QT_VERSION >= 0x050000
+    enum class HighlightNickType {
+#else
+    enum HighlightNickType {
+#endif
+        NoNick = 0x00,      ///< Don't match any nickname
+        CurrentNick = 0x01, ///< Match the current nickname
+        AllNicks = 0x02     ///< Match all configured nicknames in the chosen identity
+    };
+    // NOTE: Keep this in sync with HighlightRuleManager::HighlightNickType and
+    // NotificationSettings::HighlightNickType!
+
+    /**
+     * Construct an empty NicknameMatcher
+     */
+    NickHighlightMatcher() {}
+
+    /**
+     * Construct a configured NicknameMatcher
+     *
+     * @param highlightMode    Nickname highlighting mode
+     * @param isCaseSensitive  If true, nick matching is case-sensitive, otherwise case-insensitive
+     */
+    NickHighlightMatcher(HighlightNickType highlightMode, bool isCaseSensitive)
+        : _highlightMode(highlightMode),
+          _isCaseSensitive(isCaseSensitive) {}
+
+    /**
+     * Gets the nickname highlighting policy
+     *
+     * @return HighlightNickType for the given network
+     */
+    inline HighlightNickType highlightMode() const { return _highlightMode; }
+
+    /**
+     * Sets the nickname highlighting policy
+     *
+     * @param highlightMode Nickname highlighting mode
+     */
+    void setHighlightMode(HighlightNickType highlightMode) {
+        if (_highlightMode != highlightMode) {
+            _highlightMode = highlightMode;
+            invalidateNickCache();
+        }
+    }
+
+    /**
+     * Gets the nickname case-sensitivity policy
+     *
+     * @return True if nickname highlights are case-sensitive, otherwise false
+     */
+    inline bool isCaseSensitive() const { return _isCaseSensitive; }
+
+    /**
+     * Sets the nickname case-sensitivity policy
+     *
+     * @param isCaseSensitive If true, nick matching is case-sensitive, otherwise case-insensitive
+     */
+    void setCaseSensitive(bool isCaseSensitive) {
+        if (_isCaseSensitive != isCaseSensitive) {
+            _isCaseSensitive = isCaseSensitive;
+            invalidateNickCache();
+        }
+    }
+
+    /**
+     * Checks if the given string matches the specified network's nickname matcher
+     *
+     * Updates cache when called if needed.
+     *
+     * @param string         String to match against
+     * @param netId          Network ID of source network
+     * @param currentNick    Current nickname
+     * @param identityNicks  All nicknames configured for the current identity
+     * @return True if match found, otherwise false
+     */
+    bool match(const QString &string, const NetworkId &netId, const QString &currentNick,
+               const QStringList &identityNicks) const;
+
+public slots:
+    /**
+     * Removes the specified network ID from the cache
+     *
+     * @param netId Network ID of source network
+     */
+    void removeNetwork(const NetworkId &netId) {
+        // Remove the network from the cache list
+        if (_nickMatchCache.remove(netId) > 0) {
+            qDebug() << "Cleared nickname matching cache for removed network ID" << netId;
+        }
+    }
+
+private:
+    struct NickMatchCache {
+        // These represent internal cache and should be safe to mutate in 'const' functions
+        QString nickCurrent = {};        ///< Last cached current nick
+        QStringList identityNicks = {};  ///< Last cached identity nicks
+        ExpressionMatch matcher = {};    ///< Expression match cache for nicks
+    };
+
+    /**
+     * Update internal cache of nickname matching if needed
+     *
+     * @param netId          Network ID of source network
+     * @param currentNick    Current nickname
+     * @param identityNicks  All nicknames configured for the current identity
+     */
+    void determineExpressions(const NetworkId &netId, const QString &currentNick,
+                              const QStringList &identityNicks) const;
+
+    /**
+     * Invalidate all nickname match caches
+     *
+     * Use this after changing global configuration.
+     */
+    inline void invalidateNickCache() {
+        // Mark all as invalid
+        if (_nickMatchCache.size() > 0) {
+            _nickMatchCache.clear();
+            qDebug() << "Cleared all nickname matching cache (settings changed)";
+        }
+    }
+
+    // Global nickname configuration
+    /// Nickname highlighting mode
+    HighlightNickType _highlightMode = HighlightNickType::CurrentNick;
+    bool _isCaseSensitive = false;  ///< If true, match nicknames with exact case
+
+    // These represent internal cache and should be safe to mutate in 'const' functions
+    mutable QHash<NetworkId, NickMatchCache> _nickMatchCache; ///< Per-network nick matching cache
+
+};