common: Allow skipping negotiation of IRCv3 caps
authorShane Synan <digitalcircuit36939@gmail.com>
Sun, 19 Jul 2020 20:28:21 +0000 (16:28 -0400)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sat, 28 Nov 2020 12:42:31 +0000 (13:42 +0100)
Add "skipCaps" to Network, storing a list of IRCv3 capabilities to
skip during capability negotiation.  This allows selectively disabling
capabilities on specific IRC networks.  Some networks might not behave
as expected, and some IRC features (e.g. "echo-message" support) are
enough of a departure from traditional IRC behavior that users may
want to turn it off.

Add the necessary migrations and setup to SQLite and PostgreSQL.

Add the "SkipIrcCaps" feature flag to signal protocol support.

Fix up a comment in CoreNetwork referring to an old variable name.

Update the SQL README to mention the Serializers protocol checks.
Missing this caused me a few hours of confusion over why the protocol
change wasn't being received :)

21 files changed:
src/common/network.cpp
src/common/network.h
src/common/quassel.h
src/common/serializers/serializers.cpp
src/core/SQL/PostgreSQL/insert_network.sql
src/core/SQL/PostgreSQL/migrate_write_network.sql
src/core/SQL/PostgreSQL/select_networks_for_user.sql
src/core/SQL/PostgreSQL/setup_040_network.sql
src/core/SQL/PostgreSQL/update_network.sql
src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql [new file with mode: 0644]
src/core/SQL/README.md
src/core/SQL/SQLite/insert_network.sql
src/core/SQL/SQLite/migrate_read_network.sql
src/core/SQL/SQLite/select_networks_for_user.sql
src/core/SQL/SQLite/setup_020_network.sql
src/core/SQL/SQLite/update_network.sql
src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql [new file with mode: 0644]
src/core/abstractsqlstorage.h
src/core/corenetwork.cpp
src/core/postgresqlstorage.cpp
src/core/sqlitestorage.cpp

index 4117bf0..d6ee3c6 100644 (file)
@@ -105,6 +105,7 @@ NetworkInfo Network::networkInfo() const
     info.serverList = serverList();
     info.useRandomServer = useRandomServer();
     info.perform = perform();
+    info.skipCaps = skipCaps();
     info.useAutoIdentify = useAutoIdentify();
     info.autoIdentifyService = autoIdentifyService();
     info.autoIdentifyPassword = autoIdentifyPassword();
@@ -142,6 +143,8 @@ void Network::setNetworkInfo(const NetworkInfo& info)
         setUseRandomServer(info.useRandomServer);
     if (info.perform != perform())
         setPerform(info.perform);
+    if (info.skipCaps != skipCaps())
+        setSkipCaps(info.skipCaps);
     if (info.useAutoIdentify != useAutoIdentify())
         setUseAutoIdentify(info.useAutoIdentify);
     if (info.autoIdentifyService != autoIdentifyService())
@@ -638,6 +641,19 @@ void Network::setPerform(const QStringList& perform)
     emit configChanged();
 }
 
+void Network::setSkipCaps(const QStringList& skipCaps)
+{
+    _skipCaps = skipCaps;
+    // Ensure the list of skipped capabilities remains sorted
+    //
+    // This becomes important in CoreNetwork::beginCapNegotiation() when finding the intersection of
+    // available capabilities and skipped capabilities.  It's a bit more efficient to sort on first
+    // initialization and changes afterwards instead of on every (re)connection to the IRC network.
+    _skipCaps.sort();
+    SYNC(ARG(skipCaps))
+    emit configChanged();
+}
+
 void Network::setUseAutoIdentify(bool use)
 {
     _useAutoIdentify = use;
@@ -1109,11 +1125,42 @@ void Network::determinePrefixes() const
  * NetworkInfo
  ************************************************************************/
 
+QString NetworkInfo::skipCapsToString() const {
+    // Sort the list of capabilities when rendering to a string.  This isn't required as
+    // Network::setSkipCaps() will sort as well, but this looks nicer when displayed to the user.
+    // This also results in the list being sorted before storing in the database, too.
+    auto sortedSkipCaps = skipCaps;
+    sortedSkipCaps.sort();
+
+    // IRCv3 capabilities are transmitted space-separated, so it should be safe to assume spaces
+    // won't ever be inside them
+    //
+    // See https://ircv3.net/specs/core/capability-negotiation
+    return sortedSkipCaps.join(" ");
+}
+
+void NetworkInfo::skipCapsFromString(const QString& flattenedSkipCaps) {
+    // IRCv3 capabilities should all use lowercase capability names, though it's not strictly
+    // required by the specification.  Quassel currently converts all caps to lowercase before doing
+    // any comparisons.
+    //
+    // This would only become an issue if two capabilities have the same name and only differ by
+    // case, or if an IRC server transmits an uppercase capability and compares case-sensitively.
+    //
+    // (QString::toLower() is always done in the C locale, so locale-dependent case-sensitivity
+    //  won't ever be an issue, thankfully.)
+    //
+    // See Network::addCap(), Network::acknowledgeCap(), and friends
+    // And https://ircv3.net/specs/core/capability-negotiation
+    skipCaps = flattenedSkipCaps.toLower().split(" ", QString::SplitBehavior::SkipEmptyParts);
+}
+
 bool NetworkInfo::operator==(const NetworkInfo& other) const
 {
     return     networkName               == other.networkName
             && serverList                == other.serverList
             && perform                   == other.perform
+            && skipCaps                  == other.skipCaps
             && autoIdentifyService       == other.autoIdentifyService
             && autoIdentifyPassword      == other.autoIdentifyPassword
             && saslAccount               == other.saslAccount
@@ -1149,6 +1196,7 @@ QDataStream& operator<<(QDataStream& out, const NetworkInfo& info)
     i["NetworkName"]               = info.networkName;
     i["ServerList"]                = toVariantList(info.serverList);
     i["Perform"]                   = info.perform;
+    i["SkipCaps"]                  = info.skipCaps;
     i["AutoIdentifyService"]       = info.autoIdentifyService;
     i["AutoIdentifyPassword"]      = info.autoIdentifyPassword;
     i["SaslAccount"]               = info.saslAccount;
@@ -1181,6 +1229,7 @@ QDataStream& operator>>(QDataStream& in, NetworkInfo& info)
     info.networkName               = i["NetworkName"].toString();
     info.serverList                = fromVariantList<Network::Server>(i["ServerList"].toList());
     info.perform                   = i["Perform"].toStringList();
+    info.skipCaps                  = i["SkipCaps"].toStringList();
     info.autoIdentifyService       = i["AutoIdentifyService"].toString();
     info.autoIdentifyPassword      = i["AutoIdentifyPassword"].toString();
     info.saslAccount               = i["SaslAccount"].toString();
@@ -1210,7 +1259,8 @@ QDebug operator<<(QDebug dbg, const NetworkInfo& i)
     dbg.nospace() << "(id = " << i.networkId << " name = " << i.networkName << " identity = " << i.identity
                   << " codecForServer = " << i.codecForServer << " codecForEncoding = " << i.codecForEncoding
                   << " codecForDecoding = " << i.codecForDecoding << " serverList = " << i.serverList
-                  << " useRandomServer = " << i.useRandomServer << " perform = " << i.perform << " useAutoIdentify = " << i.useAutoIdentify
+                  << " useRandomServer = " << i.useRandomServer << " perform = " << i.perform
+                  << " skipCaps = " << i.skipCaps << " useAutoIdentify = " << i.useAutoIdentify
                   << " autoIdentifyService = " << i.autoIdentifyService << " autoIdentifyPassword = " << i.autoIdentifyPassword
                   << " useSasl = " << i.useSasl << " saslAccount = " << i.saslAccount << " saslPassword = " << i.saslPassword
                   << " useAutoReconnect = " << i.useAutoReconnect << " autoReconnectInterval = " << i.autoReconnectInterval
index ac01cc4..22e3884 100644 (file)
@@ -69,6 +69,7 @@ class COMMON_EXPORT Network : public SyncableObject
     Q_PROPERTY(int connectionState READ connectionState WRITE setConnectionState)
     Q_PROPERTY(bool useRandomServer READ useRandomServer WRITE setUseRandomServer)
     Q_PROPERTY(QStringList perform READ perform WRITE setPerform)
+    Q_PROPERTY(QStringList skipCaps READ skipCaps WRITE setSkipCaps)
     Q_PROPERTY(bool useAutoIdentify READ useAutoIdentify WRITE setUseAutoIdentify)
     Q_PROPERTY(QString autoIdentifyService READ autoIdentifyService WRITE setAutoIdentifyService)
     Q_PROPERTY(QString autoIdentifyPassword READ autoIdentifyPassword WRITE setAutoIdentifyPassword)
@@ -281,6 +282,12 @@ public:
     inline const ServerList& serverList() const { return _serverList; }
     inline bool useRandomServer() const { return _useRandomServer; }
     inline const QStringList& perform() const { return _perform; }
+    /**
+     * Gets the list of skipped (not auto-negotiated) capabilities.
+     *
+     * @returns QStringList of skippped capabilities
+     */
+    inline const QStringList skipCaps() const { return _skipCaps; }
     inline bool useAutoIdentify() const { return _useAutoIdentify; }
     inline const QString& autoIdentifyService() const { return _autoIdentifyService; }
     inline const QString& autoIdentifyPassword() const { return _autoIdentifyPassword; }
@@ -433,6 +440,12 @@ public slots:
     void setServerList(const QVariantList& serverList);
     void setUseRandomServer(bool);
     void setPerform(const QStringList&);
+    /**
+     * Sets the list of skipped (not auto-negotiated) capabilities
+     *
+     * @param skipCaps QStringList of skipped (not auto-negotiated) capabilities
+     */
+    void setSkipCaps(const QStringList& skipCaps);
     void setUseAutoIdentify(bool);
     void setAutoIdentifyService(const QString&);
     void setAutoIdentifyPassword(const QString&);
@@ -531,7 +544,7 @@ public slots:
     /**
      * Clears all capabilities from the list of available capabilities.
      *
-     * This also removes the capability from the list of acknowledged capabilities.
+     * This also removes all capabilities from the list of acknowledged capabilities.
      */
     void clearCaps();
 
@@ -566,7 +579,7 @@ public slots:
     /**
      * Initialize the list of enabled (acknowledged) capabilities.
      *
-     * @param[in] caps QVariantList of QString indicating enabled (acknowledged) capabilities and values
+     * @param[in] capsEnabled QVariantList of QString indicating enabled (acknowledged) capabilities
      */
     inline void initSetCapsEnabled(const QVariantList& capsEnabled) { _capsEnabled = fromVariantList<QString>(capsEnabled); }
     inline void initSetServerList(const QVariantList& serverList) { _serverList = fromVariantList<Server>(serverList); }
@@ -734,9 +747,11 @@ private:
     QStringList _capsEnabled;  /// Enabled capabilities that received 'CAP ACK'
     // _capsEnabled uses the same values from the <name>=<value> pairs stored in _caps
 
+
     ServerList _serverList;
     bool _useRandomServer;
     QStringList _perform;
+    QStringList _skipCaps;  ///< Capabilities to skip during negotiation (keep list sorted!)
 
     bool _useAutoIdentify;
     QString _autoIdentifyService;
@@ -779,6 +794,7 @@ struct COMMON_EXPORT NetworkInfo
 
     Network::ServerList serverList;
     QStringList perform;
+    QStringList skipCaps;           ///< Capabilities to skip during negotiation
 
     QString autoIdentifyService{"NickServ"};
     QString autoIdentifyPassword;
@@ -811,6 +827,20 @@ struct COMMON_EXPORT NetworkInfo
 public:
     bool operator==(const NetworkInfo& other) const;
     bool operator!=(const NetworkInfo& other) const;
+
+    /**
+     * Gets the list of skipped capabilities in a space-separated string
+     *
+     * @see skipCaps
+     * @return QString representing skipCaps with each cap separated by a space
+     */
+    QString skipCapsToString() const;
+    /**
+     * Sets the list of skipped capabilities from a space-separated string
+     *
+     * @param flattenedSkipCaps QString representing skipCaps with each cap separated by a space
+     */
+    void skipCapsFromString(const QString& flattenedSkipCaps);
 };
 
 COMMON_EXPORT QDataStream& operator<<(QDataStream& out, const NetworkInfo& info);
index bbacce7..7d082ec 100644 (file)
@@ -143,6 +143,7 @@ public:
         LongMessageId,        ///< 64-bit IDs for messages
         SyncedCoreInfo,       ///< CoreInfo dynamically updated using signals
         LoadBacklogForwards,  ///< Allow loading backlog in ascending order, old to new
+        SkipIrcCaps,          ///< Control what IRCv3 capabilities are skipped during negotiation
     };
     Q_ENUMS(Feature)
 
index 02bd514..60759f4 100644 (file)
@@ -255,6 +255,7 @@ bool Serializers::deserialize(QDataStream& stream, const Quassel::Features& feat
     info.serverList = fromVariantList<Network::Server>(i["ServerList"].toList());
     info.useRandomServer = i["UseRandomServer"].toBool();
     info.perform = i["Perform"].toStringList();
+    info.skipCaps = i["SkipCaps"].toStringList();
     info.useAutoIdentify = i["UseAutoIdentify"].toBool();
     info.autoIdentifyService = i["AutoIdentifyService"].toString();
     info.autoIdentifyPassword = i["AutoIdentifyPassword"].toString();
index d91f23c..9e1d8b7 100644 (file)
@@ -3,10 +3,10 @@ INSERT INTO network (userid, networkname, identityid, servercodec, encodingcodec
                      autoidentifypassword, useautoreconnect, autoreconnectinterval,
                      autoreconnectretries, unlimitedconnectretries, rejoinchannels, usesasl,
                      saslaccount, saslpassword, usecustomessagerate, messagerateburstsize,
-                     messageratedelay, unlimitedmessagerate)
+                     messageratedelay, unlimitedmessagerate, skipcaps)
 VALUES (:userid, :networkname, :identityid, :servercodec, :encodingcodec, :decodingcodec,
         :userandomserver, :perform, :useautoidentify, :autoidentifyservice, :autoidentifypassword,
         :useautoreconnect, :autoreconnectinterval, :autoreconnectretries, :unlimitedconnectretries,
         :rejoinchannels, :usesasl, :saslaccount, :saslpassword, :usecustomessagerate,
-        :messagerateburstsize, :messageratedelay, :unlimitedmessagerate)
+        :messagerateburstsize, :messageratedelay, :unlimitedmessagerate, :skipcaps)
 RETURNING networkid
index 979370f..fd1e5a2 100644 (file)
@@ -4,5 +4,5 @@ INSERT INTO network (networkid, userid, networkname, identityid, encodingcodec,
                      autoreconnectretries, unlimitedconnectretries, rejoinchannels, connected,
                      usermode, awaymessage, attachperform, detachperform, usesasl, saslaccount,
                      saslpassword, usecustomessagerate, messagerateburstsize, messageratedelay,
-                     unlimitedmessagerate)
-VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+                     unlimitedmessagerate, skipcaps)
+VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
index a5395dc..adf490b 100644 (file)
@@ -2,6 +2,6 @@ SELECT networkid, networkname, identityid, servercodec, encodingcodec, decodingc
        userandomserver, perform, useautoidentify, autoidentifyservice, autoidentifypassword,
        useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries,
        rejoinchannels, usesasl, saslaccount, saslpassword, usecustomessagerate,
-       messagerateburstsize, messageratedelay, unlimitedmessagerate
+       messagerateburstsize, messageratedelay, unlimitedmessagerate, skipcaps
 FROM network
 WHERE userid = :userid
index f1f3dbf..9e39f07 100644 (file)
@@ -28,5 +28,6 @@ CREATE TABLE network (
        messagerateburstsize INTEGER NOT NULL DEFAULT 5,     -- Maximum messages at once
        messageratedelay INTEGER NOT NULL DEFAULT 2200,      -- Delay between future messages (milliseconds)
        unlimitedmessagerate boolean NOT NULL DEFAULT FALSE, -- Disable rate limits
+       skipcaps TEXT,                                       -- Space-separated IRCv3 caps to not auto-negotiate
        UNIQUE (userid, networkname)
 )
index e087e55..83dd20d 100644 (file)
@@ -20,6 +20,7 @@ unlimitedmessagerate = :unlimitedmessagerate,
 rejoinchannels = :rejoinchannels,
 usesasl = :usesasl,
 saslaccount = :saslaccount,
-saslpassword = :saslpassword
+saslpassword = :saslpassword,
+skipcaps = :skipcaps
 WHERE userid = :userid AND networkid = :networkid
 
diff --git a/src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql b/src/core/SQL/PostgreSQL/version/31/upgrade_000_alter_network_add_skipcaps.sql
new file mode 100644 (file)
index 0000000..b67cc0d
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE network ADD COLUMN skipcaps TEXT
index 31e43f8..f7151d7 100644 (file)
@@ -168,6 +168,9 @@ In client-side code, test for a feature with...
 if (Client::isCoreFeatureEnabled(Quassel::Feature::FeatureName)) { ... }
 ```
 
+Depending on the type of protocol change, you might also need to update
+[`serializers.cpp`][file-cpp-serializers] and related files.
+
 8.  **Test everything!  Upgrade, migrate, new setups, new client/old core,
 old client/new core, etc.**
 
@@ -245,3 +248,4 @@ Thank you for reading this guide and good luck with your changes!
 [file-cpp-sqlite]: ../sqlitestorage.cpp
 [file-sh-upgradeschema]: upgradeSchema.sh
 [file-h-quassel]: ../../common/quassel.h
+[file-cpp-serializers]: ../../common/serializers/serializers.cpp
index ec5a212..1db3774 100644 (file)
@@ -3,9 +3,9 @@ INSERT INTO network (userid, networkname, identityid, servercodec, encodingcodec
                      autoidentifypassword, useautoreconnect, autoreconnectinterval,
                      autoreconnectretries, unlimitedconnectretries, rejoinchannels, usesasl,
                      saslaccount, saslpassword, usecustomessagerate, messagerateburstsize,
-                     messageratedelay, unlimitedmessagerate)
+                     messageratedelay, unlimitedmessagerate, skipcaps)
 VALUES (:userid, :networkname, :identityid, :servercodec, :encodingcodec, :decodingcodec,
         :userandomserver, :perform, :useautoidentify, :autoidentifyservice, :autoidentifypassword,
         :useautoreconnect, :autoreconnectinterval, :autoreconnectretries, :unlimitedconnectretries,
         :rejoinchannels, :usesasl, :saslaccount, :saslpassword, :usecustomessagerate,
-        :messagerateburstsize, :messageratedelay, :unlimitedmessagerate)
+        :messagerateburstsize, :messageratedelay, :unlimitedmessagerate, :skipcaps)
index cccaed9..8bbcb03 100644 (file)
@@ -3,5 +3,5 @@ SELECT networkid, userid, networkname, identityid, encodingcodec, decodingcodec,
        useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries,
        rejoinchannels, connected, usermode, awaymessage, attachperform, detachperform,
        usesasl, saslaccount, saslpassword, usecustomessagerate, messagerateburstsize,
-       messageratedelay, unlimitedmessagerate
+       messageratedelay, unlimitedmessagerate, skipcaps
 FROM network
index a5395dc..adf490b 100644 (file)
@@ -2,6 +2,6 @@ SELECT networkid, networkname, identityid, servercodec, encodingcodec, decodingc
        userandomserver, perform, useautoidentify, autoidentifyservice, autoidentifypassword,
        useautoreconnect, autoreconnectinterval, autoreconnectretries, unlimitedconnectretries,
        rejoinchannels, usesasl, saslaccount, saslpassword, usecustomessagerate,
-       messagerateburstsize, messageratedelay, unlimitedmessagerate
+       messagerateburstsize, messageratedelay, unlimitedmessagerate, skipcaps
 FROM network
 WHERE userid = :userid
index 60417a7..239f22b 100644 (file)
@@ -28,5 +28,6 @@ CREATE TABLE network (
        messagerateburstsize INTEGER NOT NULL DEFAULT 5, -- Maximum messages at once
        messageratedelay INTEGER NOT NULL DEFAULT 2200,  -- Delay between future messages (milliseconds)
        unlimitedmessagerate INTEGER NOT NULL DEFAULT 0, -- BOOL - Disable rate limits
+       skipcaps TEXT,                                   -- Space-separated IRCv3 caps to not auto-negotiate
        UNIQUE (userid, networkname)
 )
index 00e9f83..d5110e2 100644 (file)
@@ -20,5 +20,6 @@ messageratedelay = :messageratedelay,
 unlimitedmessagerate = :unlimitedmessagerate,
 usesasl = :usesasl,
 saslaccount = :saslaccount,
-saslpassword = :saslpassword
+saslpassword = :saslpassword,
+skipcaps = :skipcaps
 WHERE networkid = :networkid AND userid = :userid
diff --git a/src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql b/src/core/SQL/SQLite/version/32/upgrade_000_alter_network_add_skipcaps.sql
new file mode 100644 (file)
index 0000000..b67cc0d
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE network ADD COLUMN skipcaps TEXT
index c2521f5..e422795 100644 (file)
@@ -273,6 +273,7 @@ public:
         QString awaymessage;
         QString attachperform;
         QString detachperform;
+        QString skipcaps;
         NetworkId networkid;
         IdentityId identityid;
         int messagerateburstsize;
index 9a8c636..438a42a 100644 (file)
@@ -20,6 +20,8 @@
 
 #include "corenetwork.h"
 
+#include <algorithm>
+
 #include <QDebug>
 #include <QHostInfo>
 #include <QTextBoundaryFinder>
@@ -1075,6 +1077,11 @@ void CoreNetwork::resetTokenBucket()
 
 void CoreNetwork::serverCapAdded(const QString& capability)
 {
+    // Exclude skipped capabilities
+    if (skipCaps().contains(capability)) {
+        return;
+    }
+
     // Check if it's a known capability; if so, add it to the list
     // Handle special cases first
     if (capability == IrcCap::SASL) {
@@ -1099,7 +1106,7 @@ void CoreNetwork::serverCapAcknowledged(const QString& capability)
     }
 
     // Handle capabilities that require further messages sent to the IRC server
-    // If you change this list, ALSO change the list in CoreNetwork::capsRequiringServerMessages
+    // If you change this list, ALSO change the list in CoreNetwork::capsRequiringConfiguration
     if (capability == IrcCap::SASL) {
         // If SASL mechanisms specified, limit to what's accepted for authentication
         // if the current identity has a cert set, use SASL EXTERNAL
@@ -1253,6 +1260,26 @@ void CoreNetwork::retryCapsIndividually()
 
 void CoreNetwork::beginCapNegotiation()
 {
+    // Check if any available capabilities have been disabled
+    QStringList capsSkipped;
+    if (!skipCaps().isEmpty() && !caps().isEmpty()) {
+        // Find the entries that are common to skipCaps() and caps().  This represents any
+        // capabilities supported by the server that were skipped.
+
+        // Both skipCaps() and caps() are already lowercase
+        // std::set_intersection requires sorted lists, and we can't modify the original lists.
+        //
+        // skipCaps() should already be sorted.  caps() is intentionally not sorted elsewhere so
+        // Quassel can show the capabilities in the order transmitted by the network.
+        auto sortedCaps = caps();
+        sortedCaps.sort();
+
+        // Find the intersection between skipped caps and server-supplied caps
+        std::set_intersection(skipCaps().cbegin(), skipCaps().cend(),
+                              sortedCaps.cbegin(), sortedCaps.cend(),
+                              std::back_inserter(capsSkipped));
+    }
+
     if (!capsPendingNegotiation()) {
         // No capabilities are queued for request, determine the reason why
         QString capStatusMsg;
@@ -1283,6 +1310,16 @@ void CoreNetwork::beginCapNegotiation()
             capStatusMsg
         ));
 
+        if (!capsSkipped.isEmpty()) {
+            // Mention that some capabilities are skipped
+            showMessage(NetworkInternalMessage(
+                Message::Server,
+                BufferInfo::StatusBuffer,
+                "",
+                tr("Quassel is configured to ignore some capabilities (skipped: %1)").arg(capsSkipped.join(", "))
+            ));
+        }
+
         // End any ongoing capability negotiation, allowing connection to continue
         endCapNegotiation();
         return;
@@ -1296,6 +1333,16 @@ void CoreNetwork::beginCapNegotiation()
         tr("Ready to negotiate (found: %1)").arg(caps().join(", "))
     ));
 
+    if (!capsSkipped.isEmpty()) {
+        // Mention that some capabilities are skipped
+        showMessage(NetworkInternalMessage(
+            Message::Server,
+            BufferInfo::StatusBuffer,
+            "",
+            tr("Quassel is configured to ignore some capabilities (skipped: %1)").arg(capsSkipped.join(", "))
+        ));
+    }
+
     // Build a list of queued capabilities, starting with individual, then bundled, only adding the
     // comma separator between the two if needed (both individual and bundled caps exist).
     QString queuedCapsDisplay = _capsQueuedIndividual.join(", ")
index 36301c6..2c238ba 100644 (file)
@@ -800,6 +800,8 @@ void PostgreSqlStorage::bindNetworkInfo(QSqlQuery& query, const NetworkInfo& inf
     query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
     query.bindValue(":messageratedelay", info.messageRateDelay);
     query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate);
+    query.bindValue(":skipcaps", info.skipCapsToString());
+
     if (info.networkId.isValid())
         query.bindValue(":networkid", info.networkId.toInt());
 }
@@ -947,6 +949,7 @@ std::vector<NetworkInfo> PostgreSqlStorage::networks(UserId user)
         net.messageRateBurstSize = networksQuery.value(20).toUInt();
         net.messageRateDelay = networksQuery.value(21).toUInt();
         net.unlimitedMessageRate = networksQuery.value(22).toBool();
+        net.skipCapsFromString(networksQuery.value(23).toString());
 
         serversQuery.bindValue(":networkid", net.networkId.toInt());
         safeExec(serversQuery);
@@ -2424,6 +2427,8 @@ bool PostgreSqlMigrationWriter::writeMo(const NetworkMO& network)
     bindValue(26, network.messagerateburstsize);
     bindValue(27, network.messageratedelay);
     bindValue(28, network.unlimitedmessagerate);
+    // Skipped IRCv3 caps
+    bindValue(29, network.skipcaps);
     return exec();
 }
 
index df5033a..8ee8f88 100644 (file)
@@ -773,6 +773,7 @@ void SqliteStorage::bindNetworkInfo(QSqlQuery& query, const NetworkInfo& info)
     query.bindValue(":messagerateburstsize", info.messageRateBurstSize);
     query.bindValue(":messageratedelay", info.messageRateDelay);
     query.bindValue(":unlimitedmessagerate", info.unlimitedMessageRate ? 1 : 0);
+    query.bindValue(":skipcaps", info.skipCapsToString());
     if (info.networkId.isValid())
         query.bindValue(":networkid", info.networkId.toInt());
 }
@@ -970,6 +971,7 @@ std::vector<NetworkInfo> SqliteStorage::networks(UserId user)
                 net.messageRateBurstSize = networksQuery.value(20).toUInt();
                 net.messageRateDelay = networksQuery.value(21).toUInt();
                 net.unlimitedMessageRate = networksQuery.value(22).toInt() == 1 ? true : false;
+                net.skipCapsFromString(networksQuery.value(23).toString());
 
                 serversQuery.bindValue(":networkid", net.networkId.toInt());
                 safeExec(serversQuery);
@@ -2494,6 +2496,8 @@ bool SqliteMigrationReader::readMo(NetworkMO& network)
     network.messagerateburstsize = value(26).toInt();
     network.messageratedelay = value(27).toUInt();
     network.unlimitedmessagerate = value(28).toInt() == 1 ? true : false;
+    // Skipped IRCv3 caps
+    network.skipcaps = value(29).toString();
     return true;
 }