common: Represent core/client features as string list in the protocol
authorManuel Nickschas <sputnick@quassel-irc.org>
Wed, 21 Mar 2018 23:58:46 +0000 (00:58 +0100)
committerManuel Nickschas <sputnick@quassel-irc.org>
Mon, 26 Mar 2018 23:04:49 +0000 (01:04 +0200)
The previous way of representing all supported features as a bit-wise
combination of flags is reaching its limits, due to the fact that the
old Features enum didn't have a defined width, and thus the compiler
can (and does) choose to represent it as a 16 bit value even though
the serialization in the protocol is defined as a 32 bit value.

In order to solve this problem, and to make feature negotiation more
robust, a new field "FeatureList" is added to both "ClientInit"
and "ClientInitAck" handshake messages that holds a string list with
the supported features.

Clients still send both "Features" and "FeatureList" fields when
registering, in order to support older cores. The core now omits
the legacy "CoreFeatures" field if the client indicates support
for extended features. Support for extended features is indicated
as the legacy feature 0x8000.

Note that legacy features are a subset of the new extended features.
Clients supporting extended features are encouraged to send the
full list, including legacy ones, in their FeatureList field.

Internally, the string-based feature list is mapped to a
std::vector<bool> to provide for fast lookup of enabled features.
The new methods Client::isCoreFeatureEnabled() and Peer::hasFeature()
allow for convenient checking.

Both client and core now output a log message if there is a mismatch
between the peers.

29 files changed:
src/client/client.cpp
src/client/client.h
src/client/clientauthhandler.cpp
src/client/clientauthhandler.h
src/client/coreconnection.cpp
src/client/coreconnection.h
src/client/networkmodel.cpp
src/common/internalpeer.cpp
src/common/message.cpp
src/common/peer.cpp
src/common/peer.h
src/common/protocol.h
src/common/protocols/datastream/datastreampeer.cpp
src/common/protocols/legacy/legacypeer.cpp
src/common/quassel.cpp
src/common/quassel.h
src/common/signalproxy.cpp
src/core/SQL/upgradeSchema.sh
src/core/coreapplication.cpp
src/core/coreauthhandler.cpp
src/qtui/coreconfigwizard.cpp
src/qtui/coresessionwidget.cpp
src/qtui/mainwin.cpp
src/qtui/qtuiapplication.cpp
src/qtui/qtuimessageprocessor.cpp
src/qtui/settingspages/bufferviewsettingspage.cpp
src/qtui/settingspages/chatviewsettingspage.cpp
src/qtui/settingspages/identityeditwidget.cpp
src/qtui/settingspages/networkssettingspage.cpp

index dd20ca0..d6dc3d1 100644 (file)
@@ -56,7 +56,6 @@
 #include <stdlib.h>
 
 QPointer<Client> Client::instanceptr = 0;
 #include <stdlib.h>
 
 QPointer<Client> Client::instanceptr = 0;
-Quassel::Features Client::_coreFeatures = 0;
 
 /*** Initialization/destruction ***/
 
 
 /*** Initialization/destruction ***/
 
@@ -188,9 +187,9 @@ AbstractUi *Client::mainUi()
 }
 
 
 }
 
 
-void Client::setCoreFeatures(Quassel::Features features)
+bool Client::isCoreFeatureEnabled(Quassel::Feature feature)
 {
 {
-    _coreFeatures = features;
+    return coreConnection()->peer() ? coreConnection()->peer()->hasFeature(feature) : false;
 }
 
 
 }
 
 
@@ -430,7 +429,7 @@ void Client::setSyncedToCore()
     // create TransferManager and DccConfig if core supports them
     Q_ASSERT(!_dccConfig);
     Q_ASSERT(!_transferManager);
     // create TransferManager and DccConfig if core supports them
     Q_ASSERT(!_dccConfig);
     Q_ASSERT(!_transferManager);
-    if (coreFeatures() & Quassel::DccFileTransfer) {
+    if (isCoreFeatureEnabled(Quassel::Feature::DccFileTransfer)) {
         _dccConfig = new DccConfig(this);
         p->synchronize(dccConfig());
         _transferManager = new ClientTransferManager(this);
         _dccConfig = new DccConfig(this);
         p->synchronize(dccConfig());
         _transferManager = new ClientTransferManager(this);
@@ -461,7 +460,7 @@ void Client::finishConnectionInitialization()
     disconnect(bufferSyncer(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
 
     requestInitialBacklog();
     disconnect(bufferSyncer(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
 
     requestInitialBacklog();
-    if (coreFeatures().testFlag(Quassel::BufferActivitySync))
+    if (isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync))
         bufferSyncer()->markActivitiesChanged();
 }
 
         bufferSyncer()->markActivitiesChanged();
 }
 
@@ -484,7 +483,6 @@ void Client::disconnectFromCore()
 void Client::setDisconnectedFromCore()
 {
     _connected = false;
 void Client::setDisconnectedFromCore()
 {
     _connected = false;
-    _coreFeatures = 0;
 
     emit disconnected();
     emit coreConnectionStateChanged(false);
 
     emit disconnected();
     emit coreConnectionStateChanged(false);
index 225caab..6732d59 100644 (file)
@@ -131,9 +131,7 @@ public:
     static inline CoreAccountModel *coreAccountModel() { return instance()->_coreAccountModel; }
     static inline CoreConnection *coreConnection() { return instance()->_coreConnection; }
     static inline CoreAccount currentCoreAccount() { return coreConnection()->currentAccount(); }
     static inline CoreAccountModel *coreAccountModel() { return instance()->_coreAccountModel; }
     static inline CoreConnection *coreConnection() { return instance()->_coreConnection; }
     static inline CoreAccount currentCoreAccount() { return coreConnection()->currentAccount(); }
-    static inline Quassel::Features coreFeatures() { return _coreFeatures; }
-
-    static void setCoreFeatures(Quassel::Features features);
+    static bool isCoreFeatureEnabled(Quassel::Feature feature);
 
     static bool isConnected();
     static bool internalCore();
 
     static bool isConnected();
     static bool internalCore();
@@ -282,7 +280,6 @@ private:
     QHash<IdentityId, Identity *> _identities;
 
     bool _connected;
     QHash<IdentityId, Identity *> _identities;
 
     bool _connected;
-    static Quassel::Features _coreFeatures;
 
     QString _debugLogBuffer;
     QTextStream _debugLog;
 
     QString _debugLogBuffer;
     QTextStream _debugLog;
index 2c96a1c..54afcd3 100644 (file)
@@ -32,6 +32,7 @@
 
 #include "client.h"
 #include "clientsettings.h"
 
 #include "client.h"
 #include "clientsettings.h"
+#include "logger.h"
 #include "peerfactory.h"
 
 #if QT_VERSION < 0x050000
 #include "peerfactory.h"
 
 #if QT_VERSION < 0x050000
@@ -42,7 +43,7 @@ using namespace Protocol;
 
 ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
     : AuthHandler(parent),
 
 ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
     : AuthHandler(parent),
-    _peer(0),
+    _peer(nullptr),
     _account(account),
     _probing(false),
     _legacy(false),
     _account(account),
     _probing(false),
     _legacy(false),
@@ -52,6 +53,12 @@ ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
 }
 
 
 }
 
 
+Peer *ClientAuthHandler::peer() const
+{
+    return _peer;
+}
+
+
 void ClientAuthHandler::connectToCore()
 {
     CoreAccountSettings s;
 void ClientAuthHandler::connectToCore()
 {
     CoreAccountSettings s;
@@ -299,7 +306,7 @@ void ClientAuthHandler::startRegistration()
     useSsl = _account.useSsl();
 #endif
 
     useSsl = _account.useSsl();
 #endif
 
-    _peer->dispatch(RegisterClient(Quassel::buildInfo().fancyVersionString, Quassel::buildInfo().commitDate, useSsl, Quassel::features()));
+    _peer->dispatch(RegisterClient(Quassel::Features{}, Quassel::buildInfo().fancyVersionString, Quassel::buildInfo().commitDate, useSsl));
 }
 
 
 }
 
 
@@ -316,8 +323,7 @@ void ClientAuthHandler::handle(const ClientRegistered &msg)
     _backendInfo = msg.backendInfo;
     _authenticatorInfo = msg.authenticatorInfo;
 
     _backendInfo = msg.backendInfo;
     _authenticatorInfo = msg.authenticatorInfo;
 
-    Client::setCoreFeatures(Quassel::Features(msg.coreFeatures));
-    SignalProxy::current()->sourcePeer()->setFeatures(Quassel::Features(msg.coreFeatures));
+    _peer->setFeatures(std::move(msg.features));
 
     // The legacy protocol enables SSL at this point
     if(_legacy && _account.useSsl())
 
     // The legacy protocol enables SSL at this point
     if(_legacy && _account.useSsl())
@@ -329,6 +335,15 @@ void ClientAuthHandler::handle(const ClientRegistered &msg)
 
 void ClientAuthHandler::onConnectionReady()
 {
 
 void ClientAuthHandler::onConnectionReady()
 {
+    const auto &coreFeatures = _peer->features();
+    auto unsupported = coreFeatures.toStringList(false);
+    if (!unsupported.isEmpty()) {
+        quInfo() << qPrintable(tr("Core does not support the following features: %1").arg(unsupported.join(", ")));
+    }
+    if (!coreFeatures.unknownFeatures().isEmpty()) {
+        quInfo() << qPrintable(tr("Core supports unknown features: %1").arg(coreFeatures.unknownFeatures().join(", ")));
+    }
+
     emit connectionReady();
     emit statusMessage(tr("Connected to %1").arg(_account.accountName()));
 
     emit connectionReady();
     emit statusMessage(tr("Connected to %1").arg(_account.accountName()));
 
index 5ebd530..b80397f 100644 (file)
@@ -18,8 +18,7 @@
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef CLIENTAUTHHANDLER_H
-#define CLIENTAUTHHANDLER_H
+#pragma once
 
 #include "compressor.h"
 #include "authhandler.h"
 
 #include "compressor.h"
 #include "authhandler.h"
@@ -34,14 +33,16 @@ class ClientAuthHandler : public AuthHandler
     Q_OBJECT
 
 public:
     Q_OBJECT
 
 public:
-    ClientAuthHandler(CoreAccount account, QObject *parent = 0);
-
     enum DigestVersion {
         Md5,
         Sha2_512,
         Latest=Sha2_512
     };
 
     enum DigestVersion {
         Md5,
         Sha2_512,
         Latest=Sha2_512
     };
 
+    ClientAuthHandler(CoreAccount account, QObject *parent = 0);
+
+    Peer *peer() const;
+
 public slots:
     void connectToCore();
 
 public slots:
     void connectToCore();
 
@@ -119,5 +120,3 @@ private:
     bool _legacy;
     quint8 _connectionFeatures;
 };
     bool _legacy;
     quint8 _connectionFeatures;
 };
-
-#endif
index b0c4feb..7906128 100644 (file)
@@ -184,6 +184,15 @@ void CoreConnection::onlineStateChanged(bool isOnline)
 }
 
 
 }
 
 
+QPointer<Peer> CoreConnection::peer() const
+{
+    if (_peer) {
+        return _peer;
+    }
+    return _authHandler ? _authHandler->peer() : nullptr;
+}
+
+
 bool CoreConnection::isEncrypted() const
 {
     return _peer && _peer->isSecure();
 bool CoreConnection::isEncrypted() const
 {
     return _peer && _peer->isSecure();
@@ -463,9 +472,6 @@ void CoreConnection::onHandshakeComplete(RemotePeer *peer, const Protocol::Sessi
 void CoreConnection::internalSessionStateReceived(const Protocol::SessionState &sessionState)
 {
     updateProgress(100, 100);
 void CoreConnection::internalSessionStateReceived(const Protocol::SessionState &sessionState)
 {
     updateProgress(100, 100);
-
-    Client::setCoreFeatures(Quassel::features()); // mono connection...
-
     setState(Synchronizing);
     syncToCore(sessionState);
 }
     setState(Synchronizing);
     syncToCore(sessionState);
 }
index 4a1766e..33f91ca 100644 (file)
@@ -73,7 +73,7 @@ public:
     //! Check if we consider the last connect as reconnect
     bool wasReconnect() const { return _wasReconnect; }
 
     //! Check if we consider the last connect as reconnect
     bool wasReconnect() const { return _wasReconnect; }
 
-    QPointer<Peer> peer() { return _peer; }
+    QPointer<Peer> peer() const;
 
 public slots:
     bool connectToCore(AccountId = 0);
 
 public slots:
     bool connectToCore(AccountId = 0);
index 147605c..582b008 100644 (file)
@@ -298,7 +298,7 @@ void BufferItem::setActivityLevel(BufferInfo::ActivityLevel level)
 
 void BufferItem::clearActivityLevel()
 {
 
 void BufferItem::clearActivityLevel()
 {
-    if (Client::coreFeatures().testFlag(Quassel::Feature::BufferActivitySync)) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync)) {
         // If the core handles activity sync, clear only the highlight flag
         _activity &= ~BufferInfo::Highlight;
     } else {
         // If the core handles activity sync, clear only the highlight flag
         _activity &= ~BufferInfo::Highlight;
     } else {
@@ -307,7 +307,7 @@ void BufferItem::clearActivityLevel()
     _firstUnreadMsgId = MsgId();
 
     // FIXME remove with core proto v11
     _firstUnreadMsgId = MsgId();
 
     // FIXME remove with core proto v11
-    if (!(Client::coreFeatures() & Quassel::SynchronizedMarkerLine)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
         _markerLineMsgId = _lastSeenMsgId;
     }
 
         _markerLineMsgId = _lastSeenMsgId;
     }
 
@@ -318,7 +318,7 @@ void BufferItem::clearActivityLevel()
 void BufferItem::updateActivityLevel(const Message &msg)
 {
     // If the core handles activity, and this message is not a highlight, ignore this
 void BufferItem::updateActivityLevel(const Message &msg)
 {
     // If the core handles activity, and this message is not a highlight, ignore this
-    if (Client::coreFeatures().testFlag(Quassel::Feature::BufferActivitySync) && !msg.flags().testFlag(Message::Highlight)) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync) && !msg.flags().testFlag(Message::Highlight)) {
         return;
     }
 
         return;
     }
 
@@ -344,7 +344,7 @@ void BufferItem::updateActivityLevel(const Message &msg)
 
     Message::Types type;
     // If the core handles activities, ignore types
 
     Message::Types type;
     // If the core handles activities, ignore types
-    if (Client::coreFeatures().testFlag(Quassel::Feature::BufferActivitySync)) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync)) {
         type = Message::Types();
     } else {
         type = msg.type();
         type = Message::Types();
     } else {
         type = msg.type();
@@ -434,7 +434,7 @@ void BufferItem::setLastSeenMsgId(MsgId msgId)
     _lastSeenMsgId = msgId;
 
     // FIXME remove with core protocol v11
     _lastSeenMsgId = msgId;
 
     // FIXME remove with core protocol v11
-    if (!(Client::coreFeatures() & Quassel::SynchronizedMarkerLine)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
         if (!isCurrentBuffer())
             _markerLineMsgId = msgId;
     }
         if (!isCurrentBuffer())
             _markerLineMsgId = msgId;
     }
index b724dc0..c2512a0 100644 (file)
@@ -42,7 +42,7 @@ InternalPeer::InternalPeer(QObject *parent)
     _peer(0),
     _isOpen(true)
 {
     _peer(0),
     _isOpen(true)
 {
-
+    setFeatures(Quassel::Features{});
 }
 
 
 }
 
 
index 911c935..e5ec65a 100644 (file)
@@ -60,7 +60,7 @@ QDataStream &operator<<(QDataStream &out, const Message &msg)
         << msg.bufferInfo()
         << msg.sender().toUtf8();
 
         << msg.bufferInfo()
         << msg.sender().toUtf8();
 
-    if (SignalProxy::current()->targetPeer()->features().testFlag(Quassel::Feature::SenderPrefixes))
+    if (SignalProxy::current()->targetPeer()->hasFeature(Quassel::Feature::SenderPrefixes))
         out << msg.senderPrefixes().toUtf8();
 
     out << msg.contents().toUtf8();
         out << msg.senderPrefixes().toUtf8();
 
     out << msg.contents().toUtf8();
@@ -91,7 +91,7 @@ QDataStream &operator>>(QDataStream &in, Message &msg)
     msg._sender = QString::fromUtf8(sender);
 
     QByteArray senderPrefixes;
     msg._sender = QString::fromUtf8(sender);
 
     QByteArray senderPrefixes;
-    if (SignalProxy::current()->sourcePeer()->features().testFlag(Quassel::Feature::SenderPrefixes))
+    if (SignalProxy::current()->sourcePeer()->hasFeature(Quassel::Feature::SenderPrefixes))
         in >> senderPrefixes;
     msg._senderPrefixes = QString::fromUtf8(senderPrefixes);
 
         in >> senderPrefixes;
     msg._senderPrefixes = QString::fromUtf8(senderPrefixes);
 
index 9a9e3f5..84f292e 100644 (file)
@@ -57,12 +57,17 @@ void Peer::setClientVersion(const QString &clientVersion) {
     _clientVersion = clientVersion;
 }
 
     _clientVersion = clientVersion;
 }
 
-Quassel::Features Peer::features() const {
+bool Peer::hasFeature(Quassel::Feature feature) const {
+    return _features.isEnabled(feature);
+}
+
+Quassel::Features Peer::features() const
+{
     return _features;
 }
 
 void Peer::setFeatures(Quassel::Features features) {
     return _features;
 }
 
 void Peer::setFeatures(Quassel::Features features) {
-    _features = features;
+    _features = std::move(features);
 }
 
 int Peer::id() const {
 }
 
 int Peer::id() const {
index fbc1791..becf049 100644 (file)
@@ -51,6 +51,7 @@ public:
     QString clientVersion() const;
     void setClientVersion(const QString &clientVersion);
 
     QString clientVersion() const;
     void setClientVersion(const QString &clientVersion);
 
+    bool hasFeature(Quassel::Feature feature) const;
     Quassel::Features features() const;
     void setFeatures(Quassel::Features features);
 
     Quassel::Features features() const;
     void setFeatures(Quassel::Features features);
 
index e626686..38e53c6 100644 (file)
@@ -24,6 +24,8 @@
 #include <QDateTime>
 #include <QVariantList>
 
 #include <QDateTime>
 #include <QVariantList>
 
+#include "quassel.h"
+
 namespace Protocol {
 
 const quint32 magic = 0x42b33f00;
 namespace Protocol {
 
 const quint32 magic = 0x42b33f00;
@@ -56,18 +58,19 @@ struct HandshakeMessage {
 
 struct RegisterClient : public HandshakeMessage
 {
 
 struct RegisterClient : public HandshakeMessage
 {
-    inline RegisterClient(const QString &clientVersion, const QString &buildDate, bool sslSupported = false, quint32 features = 0)
-    : clientVersion(clientVersion)
-    , buildDate(buildDate)
-    , sslSupported(sslSupported)
-    , clientFeatures(features) {}
+    inline RegisterClient(Quassel::Features clientFeatures, const QString &clientVersion, const QString &buildDate, bool sslSupported = false)
+        : features(std::move(clientFeatures))
+        , clientVersion(clientVersion)
+        , buildDate(buildDate)
+        , sslSupported(sslSupported)
+    {}
 
 
+    Quassel::Features features;
     QString clientVersion;
     QString buildDate;
 
     // this is only used by the LegacyProtocol in compat mode
     bool sslSupported;
     QString clientVersion;
     QString buildDate;
 
     // this is only used by the LegacyProtocol in compat mode
     bool sslSupported;
-    quint32 clientFeatures;
 };
 
 
 };
 
 
@@ -82,19 +85,19 @@ struct ClientDenied : public HandshakeMessage
 
 struct ClientRegistered : public HandshakeMessage
 {
 
 struct ClientRegistered : public HandshakeMessage
 {
-    inline ClientRegistered(quint32 coreFeatures, bool coreConfigured, const QVariantList &backendInfo, bool sslSupported, const QVariantList &authenticatorInfo)
-    : coreFeatures(coreFeatures)
-    , coreConfigured(coreConfigured)
-    , backendInfo(backendInfo)
-    , authenticatorInfo(authenticatorInfo)
-    , sslSupported(sslSupported)
+    inline ClientRegistered(Quassel::Features coreFeatures, bool coreConfigured, const QVariantList &backendInfo, const QVariantList &authenticatorInfo, bool sslSupported)
+        : features(std::move(coreFeatures))
+        , coreConfigured(coreConfigured)
+        , backendInfo(backendInfo)
+        , authenticatorInfo(authenticatorInfo)
+        , sslSupported(sslSupported)
     {}
 
     {}
 
-    quint32 coreFeatures;
+    Quassel::Features features;
     bool coreConfigured;
     bool coreConfigured;
+    QVariantList backendInfo; // TODO: abstract this better
 
     // The authenticatorInfo should be optional!
 
     // The authenticatorInfo should be optional!
-    QVariantList backendInfo; // TODO: abstract this better
     QVariantList authenticatorInfo;
 
     // this is only used by the LegacyProtocol in compat mode
     QVariantList authenticatorInfo;
 
     // this is only used by the LegacyProtocol in compat mode
index e91c977..6709c69 100644 (file)
@@ -24,6 +24,7 @@
 #include <QTcpSocket>
 
 #include "datastreampeer.h"
 #include <QTcpSocket>
 
 #include "datastreampeer.h"
+#include "quassel.h"
 
 using namespace Protocol;
 
 
 using namespace Protocol;
 
@@ -116,7 +117,11 @@ void DataStreamPeer::handleHandshakeMessage(const QVariantList &mapData)
     }
 
     if (msgType == "ClientInit") {
     }
 
     if (msgType == "ClientInit") {
-        handle(RegisterClient(m["ClientVersion"].toString(), m["ClientDate"].toString(), false, m["Features"].toInt())); // UseSsl obsolete
+        handle(RegisterClient{Quassel::Features{m["FeatureList"].toStringList(), Quassel::LegacyFeatures(m["Features"].toUInt())},
+                              m["ClientVersion"].toString(),
+                              m["ClientDate"].toString(),
+                              false  // UseSsl obsolete
+               });
     }
 
     else if (msgType == "ClientInitReject") {
     }
 
     else if (msgType == "ClientInitReject") {
@@ -124,7 +129,12 @@ void DataStreamPeer::handleHandshakeMessage(const QVariantList &mapData)
     }
 
     else if (msgType == "ClientInitAck") {
     }
 
     else if (msgType == "ClientInitAck") {
-        handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), false, m["Authenticators"].toList())); // SupportsSsl obsolete
+        handle(ClientRegistered{Quassel::Features{m["FeatureList"].toStringList(), Quassel::LegacyFeatures(m["CoreFeatures"].toUInt())},
+                                m["Configured"].toBool(),
+                                m["StorageBackends"].toList(),
+                                m["Authenticators"].toList(),
+                                false  // SupportsSsl obsolete
+               });
     }
 
     else if (msgType == "CoreSetupData") {
     }
 
     else if (msgType == "CoreSetupData") {
@@ -166,9 +176,10 @@ void DataStreamPeer::handleHandshakeMessage(const QVariantList &mapData)
 void DataStreamPeer::dispatch(const RegisterClient &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInit";
 void DataStreamPeer::dispatch(const RegisterClient &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInit";
+    m["Features"] = static_cast<quint32>(msg.features.toLegacyFeatures());
+    m["FeatureList"] = msg.features.toStringList();
     m["ClientVersion"] = msg.clientVersion;
     m["ClientDate"] = msg.buildDate;
     m["ClientVersion"] = msg.clientVersion;
     m["ClientDate"] = msg.buildDate;
-    m["Features"] = msg.clientFeatures;
 
     writeMessage(m);
 }
 
     writeMessage(m);
 }
@@ -186,10 +197,17 @@ void DataStreamPeer::dispatch(const ClientDenied &msg) {
 void DataStreamPeer::dispatch(const ClientRegistered &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInitAck";
 void DataStreamPeer::dispatch(const ClientRegistered &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInitAck";
-    m["CoreFeatures"] = msg.coreFeatures;
-    m["StorageBackends"] = msg.backendInfo;
-    m["Authenticators"] = msg.authenticatorInfo;
+    if (hasFeature(Quassel::Feature::ExtendedFeatures)) {
+        m["FeatureList"] = msg.features.toStringList();
+    }
+    else {
+        m["CoreFeatures"] = static_cast<quint32>(msg.features.toLegacyFeatures());
+    }
     m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
     m["LoginEnabled"] = m["Configured"] = msg.coreConfigured;
+    m["StorageBackends"] = msg.backendInfo;
+    if (hasFeature(Quassel::Feature::Authenticators)) {
+        m["Authenticators"] = msg.authenticatorInfo;
+    }
 
     writeMessage(m);
 }
 
     writeMessage(m);
 }
index ebbd164..2f0127e 100644 (file)
@@ -23,6 +23,7 @@
 #include <QTcpSocket>
 
 #include "legacypeer.h"
 #include <QTcpSocket>
 
 #include "legacypeer.h"
+#include "quassel.h"
 
 /* version.inc is no longer used for this */
 const uint protocolVersion = 10;
 
 /* version.inc is no longer used for this */
 const uint protocolVersion = 10;
@@ -151,7 +152,10 @@ void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
             socket()->setProperty("UseCompression", true);
         }
 #endif
             socket()->setProperty("UseCompression", true);
         }
 #endif
-        handle(RegisterClient(m["ClientVersion"].toString(), m["ClientDate"].toString(), m["UseSsl"].toBool(), m["Features"].toInt()));
+        handle(RegisterClient{Quassel::Features{m["FeatureList"].toStringList(), Quassel::LegacyFeatures(m["Features"].toUInt())},
+                              m["ClientVersion"].toString(),
+                              m["ClientDate"].toString(),
+                              m["UseSsl"].toBool()});
     }
 
     else if (msgType == "ClientInitReject") {
     }
 
     else if (msgType == "ClientInitReject") {
@@ -170,7 +174,11 @@ void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
             socket()->setProperty("UseCompression", true);
 #endif
 
             socket()->setProperty("UseCompression", true);
 #endif
 
-        handle(ClientRegistered(m["CoreFeatures"].toUInt(), m["Configured"].toBool(), m["StorageBackends"].toList(), m["SupportSsl"].toBool(), m["Authenticators"].toList()));
+        handle(ClientRegistered{Quassel::Features{m["FeatureList"].toStringList(), Quassel::LegacyFeatures(m["CoreFeatures"].toUInt())},
+                                m["Configured"].toBool(),
+                                m["StorageBackends"].toList(),
+                                m["Authenticators"].toList(),
+                                m["SupportSsl"].toBool()});
     }
 
     else if (msgType == "CoreSetupData") {
     }
 
     else if (msgType == "CoreSetupData") {
@@ -212,9 +220,10 @@ void LegacyPeer::handleHandshakeMessage(const QVariant &msg)
 void LegacyPeer::dispatch(const RegisterClient &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInit";
 void LegacyPeer::dispatch(const RegisterClient &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInit";
+    m["Features"] = static_cast<quint32>(msg.features.toLegacyFeatures());
+    m["FeatureList"] = msg.features.toStringList();
     m["ClientVersion"] = msg.clientVersion;
     m["ClientDate"] = msg.buildDate;
     m["ClientVersion"] = msg.clientVersion;
     m["ClientDate"] = msg.buildDate;
-    m["Features"] = msg.clientFeatures;
 
     // FIXME only in compat mode
     m["ProtocolVersion"] = protocolVersion;
 
     // FIXME only in compat mode
     m["ProtocolVersion"] = protocolVersion;
@@ -241,9 +250,16 @@ void LegacyPeer::dispatch(const ClientDenied &msg) {
 void LegacyPeer::dispatch(const ClientRegistered &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInitAck";
 void LegacyPeer::dispatch(const ClientRegistered &msg) {
     QVariantMap m;
     m["MsgType"] = "ClientInitAck";
-    m["CoreFeatures"] = msg.coreFeatures;
+    if (hasFeature(Quassel::Feature::ExtendedFeatures)) {
+        m["FeatureList"] = msg.features.toStringList();
+    }
+    else {
+        m["CoreFeatures"] = static_cast<quint32>(msg.features.toLegacyFeatures());
+    }
     m["StorageBackends"] = msg.backendInfo;
     m["StorageBackends"] = msg.backendInfo;
-    m["Authenticators"] = msg.authenticatorInfo;
+    if (hasFeature(Quassel::Feature::Authenticators)) {
+        m["Authenticators"] = msg.authenticatorInfo;
+    }
 
     // FIXME only in compat mode
     m["ProtocolVersion"] = protocolVersion;
 
     // FIXME only in compat mode
     m["ProtocolVersion"] = protocolVersion;
index 16b8887..dea2cd9 100644 (file)
@@ -34,6 +34,7 @@
 #include <QFileInfo>
 #include <QHostAddress>
 #include <QLibraryInfo>
 #include <QFileInfo>
 #include <QHostAddress>
 #include <QLibraryInfo>
+#include <QMetaEnum>
 #include <QSettings>
 #include <QTranslator>
 #include <QUuid>
 #include <QSettings>
 #include <QTranslator>
 #include <QUuid>
@@ -454,19 +455,6 @@ void Quassel::logFatalMessage(const char *msg)
 }
 
 
 }
 
 
-Quassel::Features Quassel::features()
-{
-    Features feats = 0;
-    for (int i = 1; i <= NumFeatures; i <<= 1)
-        feats |= (Feature)i;
-
-    // Disable DCC until it is ready
-    feats &= ~Feature::DccFileTransfer;
-
-    return feats;
-}
-
-
 const QString &Quassel::coreDumpFileName()
 {
     if (_coreDumpFileName.isEmpty()) {
 const QString &Quassel::coreDumpFileName()
 {
     if (_coreDumpFileName.isEmpty()) {
@@ -678,3 +666,93 @@ void Quassel::loadTranslation(const QLocale &locale)
     quasselTranslator->load(QString("%1").arg(locale.name()), translationDirPath());
 #endif
 }
     quasselTranslator->load(QString("%1").arg(locale.name()), translationDirPath());
 #endif
 }
+
+
+// ---- Quassel::Features ---------------------------------------------------------------------------------------------
+
+Quassel::Features::Features()
+{
+    QStringList features;
+
+    // TODO Qt5: Use QMetaEnum::fromType()
+    auto featureEnum = Quassel::staticMetaObject.enumerator(Quassel::staticMetaObject.indexOfEnumerator("Feature"));
+    _features.resize(featureEnum.keyCount(), true);  // enable all known features to true
+}
+
+
+Quassel::Features::Features(const QStringList &features, LegacyFeatures legacyFeatures)
+{
+    // TODO Qt5: Use QMetaEnum::fromType()
+    auto featureEnum = Quassel::staticMetaObject.enumerator(Quassel::staticMetaObject.indexOfEnumerator("Feature"));
+    _features.resize(featureEnum.keyCount(), false);
+
+    for (auto &&feature : features) {
+        int i = featureEnum.keyToValue(qPrintable(feature));
+        if (i >= 0) {
+            _features[i] = true;
+        }
+        else {
+            _unknownFeatures << feature;
+        }
+    }
+
+    if (legacyFeatures) {
+        // TODO Qt5: Use QMetaEnum::fromType()
+        auto legacyFeatureEnum = Quassel::staticMetaObject.enumerator(Quassel::staticMetaObject.indexOfEnumerator("LegacyFeature"));
+        for (quint32 mask = 0x0001; mask <= 0x8000; mask <<=1) {
+            if (static_cast<quint32>(legacyFeatures) & mask) {
+                int i = featureEnum.keyToValue(legacyFeatureEnum.valueToKey(mask));
+                if (i >= 0) {
+                    _features[i] = true;
+                }
+            }
+        }
+    }
+}
+
+
+bool Quassel::Features::isEnabled(Feature feature) const
+{
+    size_t i = static_cast<size_t>(feature);
+    return i < _features.size() ? _features[i] : false;
+}
+
+
+QStringList Quassel::Features::toStringList(bool enabled) const
+{
+    QStringList result;
+    // TODO Qt5: Use QMetaEnum::fromType()
+    auto featureEnum = Quassel::staticMetaObject.enumerator(Quassel::staticMetaObject.indexOfEnumerator("Feature"));
+    for (quint32 i = 0; i < _features.size(); ++i) {
+        if (_features[i] == enabled) {
+            result << featureEnum.key(i);
+        }
+    }
+    return result;
+}
+
+
+Quassel::LegacyFeatures Quassel::Features::toLegacyFeatures() const
+{
+    // TODO Qt5: Use LegacyFeatures (flag operators for enum classes not supported in Qt4)
+    quint32 result{0};
+    // TODO Qt5: Use QMetaEnum::fromType()
+    auto featureEnum = Quassel::staticMetaObject.enumerator(Quassel::staticMetaObject.indexOfEnumerator("Feature"));
+    auto legacyFeatureEnum = Quassel::staticMetaObject.enumerator(Quassel::staticMetaObject.indexOfEnumerator("LegacyFeature"));
+
+    for (quint32 i = 0; i < _features.size(); ++i) {
+        if (_features[i]) {
+            int v = legacyFeatureEnum.keyToValue(featureEnum.key(i));
+            if (v >= 0) {
+                result |= v;
+            }
+        }
+    }
+    return static_cast<LegacyFeatures>(result);
+}
+
+
+QStringList Quassel::Features::unknownFeatures() const
+{
+    return _unknownFeatures;
+}
index 8b10677..16e12e9 100644 (file)
 #include <vector>
 
 #include <QCoreApplication>
 #include <vector>
 
 #include <QCoreApplication>
+#include <QFile>
+#include <QObject>
 #include <QLocale>
 #include <QString>
 #include <QLocale>
 #include <QString>
+#include <QStringList>
 
 #include "abstractcliparser.h"
 
 class QFile;
 
 
 #include "abstractcliparser.h"
 
 class QFile;
 
-class Quassel
+class Quassel : public QObject
 {
 {
-    Q_DECLARE_TR_FUNCTIONS(Quassel)
+    // TODO Qt5: Use Q_GADGET
+    Q_OBJECT
 
 public:
     enum RunMode {
 
 public:
     enum RunMode {
@@ -61,38 +65,72 @@ public:
         QString organizationDomain;
     };
 
         QString organizationDomain;
     };
 
-    //! A list of features that are optional in core and/or client, but need runtime checking
-    /** Some features require an uptodate counterpart, but don't justify a protocol break.
-     *  This is what we use this enum for. Add such features to it and check at runtime on the other
-     *  side for their existence.
+    /**
+     * This enum defines the optional features supported by cores/clients prior to version 0.13.
+     *
+     * Since the number of features declared this way is limited to 16 (due to the enum not having a defined
+     * width in cores/clients prior to 0.13), and for more robustness when negotiating features on connect,
+     * the bitfield-based representation was replaced by a string-based representation in 0.13, support for
+     * which is indicated by having the ExtendedFeatures flag set. Extended features are defined in the Feature
+     * enum.
+     *
+     * @warning Do not alter this enum; new features must be added (only) to the @a Feature enum.
      *
      *
-     *  This list should be cleaned up after every protocol break, as we can assume them to be present then.
+     * @sa Feature
      */
      */
-    enum Feature {
+    enum class LegacyFeature : quint32 {
         SynchronizedMarkerLine = 0x0001,
         SynchronizedMarkerLine = 0x0001,
-        SaslAuthentication = 0x0002,
-        SaslExternal = 0x0004,
-        HideInactiveNetworks = 0x0008,
-        PasswordChange = 0x0010,
-        CapNegotiation = 0x0020,           /// IRCv3 capability negotiation, account tracking
-        VerifyServerSSL = 0x0040,          /// IRC server SSL validation
-        CustomRateLimits = 0x0080,         /// IRC server custom message rate limits
-        DccFileTransfer = 0x0100,          /// DCC file transfer support (forcefully disabled for now)
-        AwayFormatTimestamp = 0x0200,      /// Timestamp formatting in away (e.g. %%hh:mm%%)
-        Authenticators = 0x0400,           /// Whether or not the core supports auth backends.
-        BufferActivitySync = 0x0800,       /// Sync buffer activity status
-        CoreSideHighlights = 0x1000,       /// Core-Side highlight configuration and matching
-        SenderPrefixes = 0x2000,           /// Show prefixes for senders in backlog
-        RemoteDisconnect = 0x4000,         /// Allow this peer to be remotely disconnected
-
-        NumFeatures = 0x4000
+        SaslAuthentication     = 0x0002,
+        SaslExternal           = 0x0004,
+        HideInactiveNetworks   = 0x0008,
+        PasswordChange         = 0x0010,
+        CapNegotiation         = 0x0020,
+        VerifyServerSSL        = 0x0040,
+        CustomRateLimits       = 0x0080,
+        // DccFileTransfer     = 0x0100,  // never in use
+        AwayFormatTimestamp    = 0x0200,
+        Authenticators         = 0x0400,
+        BufferActivitySync     = 0x0800,
+        CoreSideHighlights     = 0x1000,
+        SenderPrefixes         = 0x2000,
+        RemoteDisconnect       = 0x4000,
+        ExtendedFeatures       = 0x8000,
     };
     };
-    Q_DECLARE_FLAGS(Features, Feature)
+    Q_FLAGS(LegacyFeature)
+    Q_DECLARE_FLAGS(LegacyFeatures, LegacyFeature)
 
 
-    //! The features the current version of Quassel supports (\sa Feature)
-    /** \return An ORed list of all enum values in Feature
+    /**
+     * A list of features that are optional in core and/or client, but need runtime checking.
+     *
+     * Some features require an uptodate counterpart, but don't justify a protocol break.
+     * This is what we use this enum for. Add such features to it and check at runtime on the other
+     * side for their existence.
+     *
+     * For feature negotiation, these enum values are serialized as strings, so order does not matter. However,
+     * do not rename existing enum values to avoid breaking compatibility.
+     *
+     * This list should be cleaned up after every protocol break, as we can assume them to be present then.
      */
      */
-    static Features features();
+    enum class Feature : quint32 {
+        SynchronizedMarkerLine,
+        SaslAuthentication,
+        SaslExternal,
+        HideInactiveNetworks,
+        PasswordChange,           ///< Remote password change
+        CapNegotiation,           ///< IRCv3 capability negotiation, account tracking
+        VerifyServerSSL,          ///< IRC server SSL validation
+        CustomRateLimits,         ///< IRC server custom message rate limits
+        AwayFormatTimestamp,      ///< Timestamp formatting in away (e.g. %%hh:mm%%)
+        Authenticators,           ///< Whether or not the core supports auth backends
+        BufferActivitySync,       ///< Sync buffer activity status
+        CoreSideHighlights,       ///< Core-Side highlight configuration and matching
+        SenderPrefixes,           ///< Show prefixes for senders in backlog
+        RemoteDisconnect,         ///< Allow this peer to be remotely disconnected
+        ExtendedFeatures,         ///< Extended features
+    };
+    Q_ENUMS(Feature)
+
+    class Features;
 
     static Quassel *instance();
 
 
     static Quassel *instance();
 
@@ -217,5 +255,72 @@ private:
     std::vector<QuitHandler> _quitHandlers;
 };
 
     std::vector<QuitHandler> _quitHandlers;
 };
 
+// --------------------------------------------------------------------------------------------------------------------
+
+/**
+ * Class representing a set of supported core/client features.
+ *
+ * @sa Quassel::Feature
+ */
+class Quassel::Features
+{
+public:
+    /**
+     * Default constructor.
+     *
+     * Creates a Feature instance with all known features (i.e., all values declared in the Quassel::Feature enum) set.
+     * This is useful for easily creating a Feature instance that represent the current version's capabilities.
+     */
+    Features();
+
+    /**
+     * Constructs a Feature instance holding the given list of features.
+     *
+     * Both the @a features and the @a legacyFeatures arguments are considered (additively).
+     * This is useful when receiving a list of features from another peer.
+     *
+     * @param features       A list of strings matching values in the Quassel::Feature enum. Strings that don't match are
+     *                       can be accessed after construction via unknownFeatures(), but are otherwise ignored.
+     * @param legacyFeatures Holds a bit-wise combination of LegacyFeature flag values, which are each added to the list of
+     *                       features represented by this Features instance.
+     */
+    Features(const QStringList &features, LegacyFeatures legacyFeatures);
 
 
-Q_DECLARE_OPERATORS_FOR_FLAGS(Quassel::Features);
+    /**
+     * Check if a given feature is marked as enabled in this Features instance.
+     *
+     * @param feature The feature to be queried
+     * @returns Whether the given feature is marked as enabled
+     */
+    bool isEnabled(Feature feature) const;
+
+    /**
+     * Provides a list of all features marked as either enabled or disabled (as indicated by the @a enabled argument) as strings.
+     *
+     * @param enabled Whether to return the enabled or the disabled features
+     * @return A string list containing all enabled or disabled features
+     */
+    QStringList toStringList(bool enabled = true) const;
+
+    /**
+     * Provides a list of all enabled legacy features (i.e. features defined prior to v0.13) as bit-wise combination in a
+     * LegacyFeatures type.
+     *
+     * @note Extended features cannot be represented this way, and are thus ignored even if set.
+     * @return A LegacyFeatures type holding the bit-wise combination of all legacy features enabled in this Features instance
+     */
+    LegacyFeatures toLegacyFeatures() const;
+
+    /**
+     * Provides the list of strings that could not be mapped to Quassel::Feature enum values on construction.
+     *
+     * Useful for debugging/logging purposes.
+     *
+     * @returns A list of strings that could not be mapped to the Feature enum on construction of this Features instance, if any
+     */
+    QStringList unknownFeatures() const;
+
+private:
+    std::vector<bool> _features;
+    QStringList _unknownFeatures;
+};
index 1d8c956..334bfa1 100644 (file)
@@ -829,7 +829,7 @@ void SignalProxy::updateSecureState()
 
 QVariantList SignalProxy::peerData() {
     QVariantList result;
 
 QVariantList SignalProxy::peerData() {
     QVariantList result;
-    for (auto peer : _peerMap.values()) {
+    for (auto &&peer : _peerMap.values()) {
         QVariantMap data;
         data["id"] = peer->id();
         data["clientVersion"] = peer->clientVersion();
         QVariantMap data;
         data["id"] = peer->id();
         data["clientVersion"] = peer->clientVersion();
@@ -839,8 +839,8 @@ QVariantList SignalProxy::peerData() {
         data["remoteAddress"] = peer->address();
         data["connectedSince"] = peer->connectedSince();
         data["secure"] = peer->isSecure();
         data["remoteAddress"] = peer->address();
         data["connectedSince"] = peer->connectedSince();
         data["secure"] = peer->isSecure();
-        int features = peer->features();
-        data["features"] = features;
+        data["features"] = static_cast<quint32>(peer->features().toLegacyFeatures());
+        data["featureList"] = peer->features().toStringList();
         result << data;
     }
     return result;
         result << data;
     }
     return result;
index 57792e0..22bbff5 100755 (executable)
@@ -89,7 +89,7 @@
 #
 # Newer clients need to detect when they're on an older core to disable the
 # feature.  Use 'enum Feature' in 'quassel.h'.  In client-side code, test with
 #
 # Newer clients need to detect when they're on an older core to disable the
 # feature.  Use 'enum Feature' in 'quassel.h'.  In client-side code, test with
-# 'if (Client::coreFeatures() & Quassel::FeatureName) { ... }'
+# 'if (Client::isCoreFeatureEnabled(Quassel::Feature::FeatureName)) { ... }'
 #
 # 9.  Test everything!  Upgrade, migrate, new setups, new client/old core,
 # old client/new core, etc.
 #
 # 9.  Test everything!  Upgrade, migrate, new setups, new client/old core,
 # old client/new core, etc.
index 1a689a2..0806a2a 100644 (file)
@@ -84,6 +84,7 @@ CoreApplication::CoreApplication(int &argc, char **argv)
 CoreApplication::~CoreApplication()
 {
     delete _internal;
 CoreApplication::~CoreApplication()
 {
     delete _internal;
+    Quassel::destroy();
 }
 
 
 }
 
 
index c01115f..a05fa95 100644 (file)
@@ -171,22 +171,23 @@ void CoreAuthHandler::handle(const RegisterClient &msg)
         return;
     }
 
         return;
     }
 
+    _peer->setFeatures(std::move(msg.features));
+    _peer->setBuildDate(msg.buildDate);
+    _peer->setClientVersion(msg.clientVersion);
+
     QVariantList backends;
     QVariantList authenticators;
     bool configured = Core::isConfigured();
     if (!configured) {
         backends = Core::backendInfo();
     QVariantList backends;
     QVariantList authenticators;
     bool configured = Core::isConfigured();
     if (!configured) {
         backends = Core::backendInfo();
-        authenticators = Core::authenticatorInfo();
+        if (_peer->hasFeature(Quassel::Feature::Authenticators)) {
+            authenticators = Core::authenticatorInfo();
+        }
     }
 
     }
 
-    // useSsl is only used for the legacy protocol
-    // XXX: FIXME: use client features here: we cannot pass authenticators if the client is too old!
-    _peer->dispatch(ClientRegistered(Quassel::features(), configured, backends, useSsl, authenticators));
-
-    _peer->setBuildDate(msg.buildDate);
-    _peer->setClientVersion(msg.clientVersion);
-    _peer->setFeatures(Quassel::Features(msg.clientFeatures));
+    _peer->dispatch(ClientRegistered(Quassel::Features{}, configured, backends, authenticators, useSsl));
 
 
+    // useSsl is only used for the legacy protocol
     if (_legacy && useSsl)
         startSsl();
 
     if (_legacy && useSsl)
         startSsl();
 
@@ -236,6 +237,15 @@ void CoreAuthHandler::handle(const Login &msg)
 
     quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).").arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
 
 
     quInfo() << qPrintable(tr("Client %1 initialized and authenticated successfully as \"%2\" (UserId: %3).").arg(socket()->peerAddress().toString(), msg.user, QString::number(uid.toInt())));
 
+    const auto &clientFeatures = _peer->features();
+    auto unsupported = clientFeatures.toStringList(false);
+    if (!unsupported.isEmpty()) {
+        quInfo() << qPrintable(tr("Client does not support the following features: %1").arg(unsupported.join(", ")));
+    }
+    if (!clientFeatures.unknownFeatures().isEmpty()) {
+        quInfo() << qPrintable(tr("Client supports unknown features: %1").arg(clientFeatures.unknownFeatures().join(", ")));
+    }
+
     disconnect(socket(), 0, this, 0);
     disconnect(_peer, 0, this, 0);
     _peer->setParent(0); // Core needs to take care of this one now!
     disconnect(socket(), 0, this, 0);
     disconnect(_peer, 0, this, 0);
     _peer->setParent(0); // Core needs to take care of this one now!
index a9453fa..4b8c7c5 100644 (file)
@@ -188,7 +188,7 @@ void CoreConfigWizard::prepareCoreSetup(const QString &backend, const QVariantMa
 
     // FIXME? We need to be able to set up older cores that don't have auth backend support.
     // So if the core doesn't support that feature, don't pass those parameters.
 
     // FIXME? We need to be able to set up older cores that don't have auth backend support.
     // So if the core doesn't support that feature, don't pass those parameters.
-    if (!(Client::coreFeatures() & Quassel::Authenticators)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) {
         coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties));
     }
     else {
         coreConnection()->setupCore(Protocol::SetupData(field("adminUser.user").toString(), field("adminUser.password").toString(), backend, properties));
     }
     else {
@@ -267,7 +267,7 @@ AdminUserPage::AdminUserPage(QWidget *parent) : QWizardPage(parent)
 int AdminUserPage::nextId() const
 {
     // If the core doesn't support auth backends, skip that page!
 int AdminUserPage::nextId() const
 {
     // If the core doesn't support auth backends, skip that page!
-    if (!(Client::coreFeatures() & Quassel::Authenticators)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::Authenticators)) {
         return CoreConfigWizard::StorageSelectionPage;
     }
     else {
         return CoreConfigWizard::StorageSelectionPage;
     }
     else {
index f3f987d..1990236 100644 (file)
@@ -44,8 +44,8 @@ void CoreSessionWidget::setData(QMap<QString, QVariant> map)
     }
     ui.labelSecure->setText(map["secure"].toBool() ? tr("Yes") : tr("No"));
 
     }
     ui.labelSecure->setText(map["secure"].toBool() ? tr("Yes") : tr("No"));
 
-    auto features = Quassel::Features(map["features"].toInt());
-    ui.disconnectButton->setVisible(features.testFlag(Quassel::Feature::RemoteDisconnect));
+    auto features = Quassel::Features{map["featureList"].toStringList(), static_cast<Quassel::LegacyFeatures>(map["features"].toUInt())};
+    ui.disconnectButton->setVisible(features.isEnabled(Quassel::Feature::RemoteDisconnect));
 
     bool success = false;
     _peerId = map["id"].toInt(&success);
 
     bool success = false;
     _peerId = map["id"].toInt(&success);
index 923a384..d41f087 100644 (file)
@@ -774,7 +774,7 @@ void MainWin::changeActiveBufferView(int bufferViewId)
 
 void MainWin::showPasswordChangeDlg()
 {
 
 void MainWin::showPasswordChangeDlg()
 {
-    if((Client::coreFeatures() & Quassel::PasswordChange)) {
+    if(Client::isCoreFeatureEnabled(Quassel::Feature::PasswordChange)) {
         PasswordChangeDlg dlg(this);
         dlg.exec();
     }
         PasswordChangeDlg dlg(this);
         dlg.exec();
     }
index bd7ec7e..77f1ac8 100644 (file)
@@ -189,6 +189,7 @@ bool QtUiApplication::init()
 QtUiApplication::~QtUiApplication()
 {
     Client::destroy();
 QtUiApplication::~QtUiApplication()
 {
     Client::destroy();
+    Quassel::destroy();
 }
 
 
 }
 
 
index de35336..40d9c28 100644 (file)
@@ -57,7 +57,7 @@ void QtUiMessageProcessor::reset()
 
 void QtUiMessageProcessor::process(Message &msg)
 {
 
 void QtUiMessageProcessor::process(Message &msg)
 {
-    if (!Client::coreFeatures().testFlag(Quassel::Feature::CoreSideHighlights))
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights))
         checkForHighlight(msg);
     preProcess(msg);
     Client::messageModel()->insertMessage(msg);
         checkForHighlight(msg);
     preProcess(msg);
     Client::messageModel()->insertMessage(msg);
@@ -69,7 +69,7 @@ void QtUiMessageProcessor::process(QList<Message> &msgs)
     QList<Message>::iterator msgIter = msgs.begin();
     QList<Message>::iterator msgIterEnd = msgs.end();
     while (msgIter != msgIterEnd) {
     QList<Message>::iterator msgIter = msgs.begin();
     QList<Message>::iterator msgIterEnd = msgs.end();
     while (msgIter != msgIterEnd) {
-        if (!Client::coreFeatures().testFlag(Quassel::Feature::CoreSideHighlights))
+        if (!Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights))
             checkForHighlight(*msgIter);
         preProcess(*msgIter);
         ++msgIter;
             checkForHighlight(*msgIter);
         preProcess(*msgIter);
         ++msgIter;
index 4bfddc4..235f48e 100644 (file)
@@ -40,7 +40,7 @@ BufferViewSettingsPage::BufferViewSettingsPage(QWidget *parent)
 {
     ui.setupUi(this);
     //Hide the hide inactive networks feature on older cores (which won't save the setting)
 {
     ui.setupUi(this);
     //Hide the hide inactive networks feature on older cores (which won't save the setting)
-    if (!(Client::coreFeatures() & Quassel::HideInactiveNetworks))
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::HideInactiveNetworks))
         ui.hideInactiveNetworks->hide();
 
     ui.renameBufferView->setIcon(QIcon::fromTheme("edit-rename"));
         ui.hideInactiveNetworks->hide();
 
     ui.renameBufferView->setIcon(QIcon::fromTheme("edit-rename"));
index 766bcd4..7d3d964 100644 (file)
@@ -34,12 +34,12 @@ ChatViewSettingsPage::ChatViewSettingsPage(QWidget *parent)
 #endif
 
     // FIXME remove with protocol v11
 #endif
 
     // FIXME remove with protocol v11
-    if (!Client::coreFeatures().testFlag(Quassel::SynchronizedMarkerLine)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
         ui.autoMarkerLine->setEnabled(false);
         ui.autoMarkerLine->setChecked(true);
         ui.autoMarkerLine->setToolTip(tr("You need at least version 0.6 of Quassel Core to use this feature"));
     }
         ui.autoMarkerLine->setEnabled(false);
         ui.autoMarkerLine->setChecked(true);
         ui.autoMarkerLine->setToolTip(tr("You need at least version 0.6 of Quassel Core to use this feature"));
     }
-    if (!Client::coreFeatures().testFlag(Quassel::Feature::CoreSideHighlights)) {
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights)) {
         ui.showSenderPrefixes->setEnabled(false);
         ui.showSenderPrefixes->setToolTip(tr("You need at least version 0.13 of Quassel Core to use this feature"));
     }
         ui.showSenderPrefixes->setEnabled(false);
         ui.showSenderPrefixes->setToolTip(tr("You need at least version 0.13 of Quassel Core to use this feature"));
     }
index 9085ae9..9b4c0ba 100644 (file)
@@ -86,7 +86,7 @@ IdentityEditWidget::IdentityEditWidget(QWidget *parent)
     ui.sslCertGroupBox->installEventFilter(this);
 #endif
 
     ui.sslCertGroupBox->installEventFilter(this);
 #endif
 
-    if (Client::coreFeatures() & Quassel::AwayFormatTimestamp) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::AwayFormatTimestamp)) {
         // Core allows formatting %%timestamp%% messages in away strings.  Update tooltips.
         QString strFormatTooltip;
         QTextStream formatTooltip( &strFormatTooltip, QIODevice::WriteOnly );
         // Core allows formatting %%timestamp%% messages in away strings.  Update tooltips.
         QString strFormatTooltip;
         QTextStream formatTooltip( &strFormatTooltip, QIODevice::WriteOnly );
index cc8dda2..6bb068d 100644 (file)
@@ -46,9 +46,9 @@ NetworksSettingsPage::NetworksSettingsPage(QWidget *parent)
     ui.setupUi(this);
 
     // hide SASL options for older cores
     ui.setupUi(this);
 
     // hide SASL options for older cores
-    if (!(Client::coreFeatures() & Quassel::SaslAuthentication))
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SaslAuthentication))
         ui.sasl->hide();
         ui.sasl->hide();
-    if (!(Client::coreFeatures() & Quassel::SaslExternal))
+    if (!Client::isCoreFeatureEnabled(Quassel::Feature::SaslExternal))
         ui.saslExtInfo->hide();
 #ifndef HAVE_SSL
     ui.saslExtInfo->hide();
         ui.saslExtInfo->hide();
 #ifndef HAVE_SSL
     ui.saslExtInfo->hide();
@@ -176,7 +176,7 @@ void NetworksSettingsPage::load()
     reset();
 
     // Handle UI dependent on core feature flags here
     reset();
 
     // Handle UI dependent on core feature flags here
-    if (Client::coreFeatures() & Quassel::CustomRateLimits) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::CustomRateLimits)) {
         // Custom rate limiting supported, allow toggling
         ui.useCustomMessageRate->setEnabled(true);
         // Reset tooltip to default.
         // Custom rate limiting supported, allow toggling
         ui.useCustomMessageRate->setEnabled(true);
         // Reset tooltip to default.
@@ -358,7 +358,7 @@ void NetworksSettingsPage::setItemState(NetworkId id, QListWidgetItem *item)
 void NetworksSettingsPage::setNetworkCapStates(NetworkId id)
 {
     const Network *net = Client::network(id);
 void NetworksSettingsPage::setNetworkCapStates(NetworkId id)
 {
     const Network *net = Client::network(id);
-    if ((Client::coreFeatures() & Quassel::CapNegotiation) && net) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::CapNegotiation) && net) {
         // Capability negotiation is supported, network exists.
         // Check if the network is connected.  Don't use net->isConnected() as that won't be true
         // during capability negotiation when capabilities are added and removed.
         // Capability negotiation is supported, network exists.
         // Check if the network is connected.  Don't use net->isConnected() as that won't be true
         // during capability negotiation when capabilities are added and removed.
@@ -575,7 +575,7 @@ void NetworksSettingsPage::displayNetwork(NetworkId id)
 
 #ifdef HAVE_SSL
         // this is only needed when the core supports SASL EXTERNAL
 
 #ifdef HAVE_SSL
         // this is only needed when the core supports SASL EXTERNAL
-        if (Client::coreFeatures() & Quassel::SaslExternal) {
+        if (Client::isCoreFeatureEnabled(Quassel::Feature::SaslExternal)) {
             if (_cid) {
                 disconnect(_cid, SIGNAL(sslSettingsUpdated()), this, SLOT(sslUpdated()));
                 delete _cid;
             if (_cid) {
                 disconnect(_cid, SIGNAL(sslSettingsUpdated()), this, SLOT(sslUpdated()));
                 delete _cid;
@@ -1021,7 +1021,7 @@ NetworkAddDlg::NetworkAddDlg(const QStringList &exist, QWidget *parent) : QDialo
     // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden.
     // If useSSL is later changed to be checked by default, change port's default value, too.
 
     // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden.
     // If useSSL is later changed to be checked by default, change port's default value, too.
 
-    if (Client::coreFeatures() & Quassel::VerifyServerSSL) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::VerifyServerSSL)) {
         // Synchronize requiring SSL with the use SSL checkbox
         ui.sslVerify->setEnabled(ui.useSSL->isChecked());
         connect(ui.useSSL, SIGNAL(toggled(bool)), ui.sslVerify, SLOT(setEnabled(bool)));
         // Synchronize requiring SSL with the use SSL checkbox
         ui.sslVerify->setEnabled(ui.useSSL->isChecked());
         connect(ui.useSSL, SIGNAL(toggled(bool)), ui.sslVerify, SLOT(setEnabled(bool)));
@@ -1168,7 +1168,7 @@ ServerEditDlg::ServerEditDlg(const Network::Server &server, QWidget *parent) : Q
     // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden.
     // If useSSL is later changed to be checked by default, change port's default value, too.
 
     // Do NOT call updateSslPort when loading settings, otherwise port settings may be overriden.
     // If useSSL is later changed to be checked by default, change port's default value, too.
 
-    if (Client::coreFeatures() & Quassel::VerifyServerSSL) {
+    if (Client::isCoreFeatureEnabled(Quassel::Feature::VerifyServerSSL)) {
         // Synchronize requiring SSL with the use SSL checkbox
         ui.sslVerify->setEnabled(ui.useSSL->isChecked());
         connect(ui.useSSL, SIGNAL(toggled(bool)), ui.sslVerify, SLOT(setEnabled(bool)));
         // Synchronize requiring SSL with the use SSL checkbox
         ui.sslVerify->setEnabled(ui.useSSL->isChecked());
         connect(ui.useSSL, SIGNAL(toggled(bool)), ui.sslVerify, SLOT(setEnabled(bool)));