#include <stdlib.h>
QPointer<Client> Client::instanceptr = 0;
-Quassel::Features Client::_coreFeatures = 0;
/*** Initialization/destruction ***/
}
-void Client::setCoreFeatures(Quassel::Features features)
+bool Client::isCoreFeatureEnabled(Quassel::Feature feature)
{
- _coreFeatures = features;
+ return coreConnection()->peer() ? coreConnection()->peer()->hasFeature(feature) : false;
}
// 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);
disconnect(bufferSyncer(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
requestInitialBacklog();
- if (coreFeatures().testFlag(Quassel::BufferActivitySync))
+ if (isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync))
bufferSyncer()->markActivitiesChanged();
}
void Client::setDisconnectedFromCore()
{
_connected = false;
- _coreFeatures = 0;
emit disconnected();
emit coreConnectionStateChanged(false);
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();
QHash<IdentityId, Identity *> _identities;
bool _connected;
- static Quassel::Features _coreFeatures;
QString _debugLogBuffer;
QTextStream _debugLog;
#include "client.h"
#include "clientsettings.h"
+#include "logger.h"
#include "peerfactory.h"
#if QT_VERSION < 0x050000
ClientAuthHandler::ClientAuthHandler(CoreAccount account, QObject *parent)
: AuthHandler(parent),
- _peer(0),
+ _peer(nullptr),
_account(account),
_probing(false),
_legacy(false),
}
+Peer *ClientAuthHandler::peer() const
+{
+ return _peer;
+}
+
+
void ClientAuthHandler::connectToCore()
{
CoreAccountSettings s;
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));
}
_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())
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()));
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
-#ifndef CLIENTAUTHHANDLER_H
-#define CLIENTAUTHHANDLER_H
+#pragma once
#include "compressor.h"
#include "authhandler.h"
Q_OBJECT
public:
- ClientAuthHandler(CoreAccount account, QObject *parent = 0);
-
enum DigestVersion {
Md5,
Sha2_512,
Latest=Sha2_512
};
+ ClientAuthHandler(CoreAccount account, QObject *parent = 0);
+
+ Peer *peer() const;
+
public slots:
void connectToCore();
bool _legacy;
quint8 _connectionFeatures;
};
-
-#endif
}
+QPointer<Peer> CoreConnection::peer() const
+{
+ if (_peer) {
+ return _peer;
+ }
+ return _authHandler ? _authHandler->peer() : nullptr;
+}
+
+
bool CoreConnection::isEncrypted() const
{
return _peer && _peer->isSecure();
void CoreConnection::internalSessionStateReceived(const Protocol::SessionState &sessionState)
{
updateProgress(100, 100);
-
- Client::setCoreFeatures(Quassel::features()); // mono connection...
-
setState(Synchronizing);
syncToCore(sessionState);
}
//! 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);
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 {
_firstUnreadMsgId = MsgId();
// FIXME remove with core proto v11
- if (!(Client::coreFeatures() & Quassel::SynchronizedMarkerLine)) {
+ if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
_markerLineMsgId = _lastSeenMsgId;
}
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;
}
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();
_lastSeenMsgId = msgId;
// FIXME remove with core protocol v11
- if (!(Client::coreFeatures() & Quassel::SynchronizedMarkerLine)) {
+ if (!Client::isCoreFeatureEnabled(Quassel::Feature::SynchronizedMarkerLine)) {
if (!isCurrentBuffer())
_markerLineMsgId = msgId;
}
_peer(0),
_isOpen(true)
{
-
+ setFeatures(Quassel::Features{});
}
<< 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();
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);
_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) {
- _features = features;
+ _features = std::move(features);
}
int Peer::id() const {
QString clientVersion() const;
void setClientVersion(const QString &clientVersion);
+ bool hasFeature(Quassel::Feature feature) const;
Quassel::Features features() const;
void setFeatures(Quassel::Features features);
#include <QDateTime>
#include <QVariantList>
+#include "quassel.h"
+
namespace Protocol {
const quint32 magic = 0x42b33f00;
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;
- quint32 clientFeatures;
};
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;
+ QVariantList backendInfo; // TODO: abstract this better
// The authenticatorInfo should be optional!
- QVariantList backendInfo; // TODO: abstract this better
QVariantList authenticatorInfo;
// this is only used by the LegacyProtocol in compat mode
#include <QTcpSocket>
#include "datastreampeer.h"
+#include "quassel.h"
using namespace Protocol;
}
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 == "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") {
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["Features"] = msg.clientFeatures;
writeMessage(m);
}
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["StorageBackends"] = msg.backendInfo;
+ if (hasFeature(Quassel::Feature::Authenticators)) {
+ m["Authenticators"] = msg.authenticatorInfo;
+ }
writeMessage(m);
}
#include <QTcpSocket>
#include "legacypeer.h"
+#include "quassel.h"
/* version.inc is no longer used for this */
const uint protocolVersion = 10;
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") {
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") {
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["Features"] = msg.clientFeatures;
// FIXME only in compat mode
m["ProtocolVersion"] = protocolVersion;
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["Authenticators"] = msg.authenticatorInfo;
+ if (hasFeature(Quassel::Feature::Authenticators)) {
+ m["Authenticators"] = msg.authenticatorInfo;
+ }
// FIXME only in compat mode
m["ProtocolVersion"] = protocolVersion;
#include <QFileInfo>
#include <QHostAddress>
#include <QLibraryInfo>
+#include <QMetaEnum>
#include <QSettings>
#include <QTranslator>
#include <QUuid>
}
-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()) {
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;
+}
#include <vector>
#include <QCoreApplication>
+#include <QFile>
+#include <QObject>
#include <QLocale>
#include <QString>
+#include <QStringList>
#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 {
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,
- 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();
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;
+};
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();
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;
#
# 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.
CoreApplication::~CoreApplication()
{
delete _internal;
+ Quassel::destroy();
}
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();
- 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();
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!
// 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 {
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 {
}
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);
void MainWin::showPasswordChangeDlg()
{
- if((Client::coreFeatures() & Quassel::PasswordChange)) {
+ if(Client::isCoreFeatureEnabled(Quassel::Feature::PasswordChange)) {
PasswordChangeDlg dlg(this);
dlg.exec();
}
QtUiApplication::~QtUiApplication()
{
Client::destroy();
+ Quassel::destroy();
}
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);
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;
{
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"));
#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"));
}
- 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.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 );
ui.setupUi(this);
// hide SASL options for older cores
- if (!(Client::coreFeatures() & Quassel::SaslAuthentication))
+ if (!Client::isCoreFeatureEnabled(Quassel::Feature::SaslAuthentication))
ui.sasl->hide();
- if (!(Client::coreFeatures() & Quassel::SaslExternal))
+ if (!Client::isCoreFeatureEnabled(Quassel::Feature::SaslExternal))
ui.saslExtInfo->hide();
#ifndef HAVE_SSL
ui.saslExtInfo->hide();
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.
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.
#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;
// 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)));
// 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)));