Make capabilities static

As discussed with Albert, move the population of capabilities into the
identity package, making them static along the execution of the link.

When we receive the identityPackage, we collect the plugins we can use with
the device and stick to those. This should simplify the implementation and
remove the possibility to lose packages if packages are received before the
capabilities are processed in the former approach.

REVIEW: 128386
This commit is contained in:
Aleix Pol 2016-07-06 17:37:22 +02:00
parent afdac88885
commit 81634303b2
9 changed files with 69 additions and 97 deletions

View file

@ -40,8 +40,6 @@
#include "kdeconnectconfig.h" #include "kdeconnectconfig.h"
#include "daemon.h" #include "daemon.h"
#define MIN_VERSION_WITH_CAPPABILITIES_SUPPORT 6
Q_LOGGING_CATEGORY(KDECONNECT_CORE, "kdeconnect.core") Q_LOGGING_CATEGORY(KDECONNECT_CORE, "kdeconnect.core")
static void warn(const QString &info) static void warn(const QString &info)
@ -69,8 +67,6 @@ Device::Device(QObject* parent, const NetworkPackage& identityPackage, DeviceLin
: QObject(parent) : QObject(parent)
, m_deviceId(identityPackage.get<QString>("deviceId")) , m_deviceId(identityPackage.get<QString>("deviceId"))
, m_deviceName(identityPackage.get<QString>("deviceName")) , m_deviceName(identityPackage.get<QString>("deviceName"))
, m_deviceType(str2type(identityPackage.get<QString>("deviceType")))
, m_protocolVersion(identityPackage.get<int>("protocolVersion", -1))
{ {
addLink(identityPackage, dl); addLink(identityPackage, dl);
@ -99,45 +95,17 @@ QStringList Device::loadedPlugins() const
void Device::reloadPlugins() void Device::reloadPlugins()
{ {
QHash<QString, KdeConnectPlugin*> newPluginMap; QHash<QString, KdeConnectPlugin*> newPluginMap;
QMultiMap<QString, KdeConnectPlugin*> newPluginsByIncomingInterface; QMultiMap<QString, KdeConnectPlugin*> newPluginsByIncomingCapability;
QMultiMap<QString, KdeConnectPlugin*> newPluginsByOutgoingInterface;
QSet<QString> supportedIncomingInterfaces;
QSet<QString> supportedOutgoingInterfaces;
QStringList unsupportedPlugins;
if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices
PluginLoader* loader = PluginLoader::instance(); PluginLoader* loader = PluginLoader::instance();
const bool capabilitiesSupported = (m_protocolVersion >= MIN_VERSION_WITH_CAPPABILITIES_SUPPORT);
Q_FOREACH (const QString& pluginName, loader->getPluginList()) { Q_FOREACH (const QString& pluginName, m_supportedPlugins) {
const KPluginMetaData service = loader->getPluginInfo(pluginName); const KPluginMetaData service = loader->getPluginInfo(pluginName);
const QSet<QString> incomingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType").toSet();
const QSet<QString> outgoingInterfaces = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-OutgoingPackageType").toSet();
const bool pluginEnabled = isPluginEnabled(pluginName); const bool pluginEnabled = isPluginEnabled(pluginName);
const QSet<QString> incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType").toSet();
if (pluginEnabled) {
supportedIncomingInterfaces += incomingInterfaces;
supportedOutgoingInterfaces += outgoingInterfaces;
}
const bool pluginNeedsCapabilities = !incomingInterfaces.isEmpty() || !outgoingInterfaces.isEmpty();
//If we don't find intersection with the received on one end and the sent on the other, we don't
//let the plugin stay
if (capabilitiesSupported && pluginNeedsCapabilities
&& (m_incomingCapabilities & outgoingInterfaces).isEmpty()
&& (m_outgoingCapabilities & incomingInterfaces).isEmpty()
) {
if (!m_incomingCapabilities.isEmpty() || !m_outgoingCapabilities.isEmpty()) {
qCWarning(KDECONNECT_CORE) << "not loading" << pluginName << "because of unmatched capabilities" <<
outgoingInterfaces << incomingInterfaces;
}
unsupportedPlugins.append(pluginName);
continue;
}
if (pluginEnabled) { if (pluginEnabled) {
KdeConnectPlugin* plugin = m_plugins.take(pluginName); KdeConnectPlugin* plugin = m_plugins.take(pluginName);
@ -145,12 +113,10 @@ void Device::reloadPlugins()
if (!plugin) { if (!plugin) {
plugin = loader->instantiatePluginForDevice(pluginName, this); plugin = loader->instantiatePluginForDevice(pluginName, this);
} }
Q_ASSERT(plugin);
Q_FOREACH (const QString& interface, incomingInterfaces) { Q_FOREACH (const QString& interface, incomingCapabilities) {
newPluginsByIncomingInterface.insert(interface, plugin); newPluginsByIncomingCapability.insert(interface, plugin);
}
Q_FOREACH (const QString& interface, outgoingInterfaces) {
newPluginsByOutgoingInterface.insert(interface, plugin);
} }
newPluginMap[pluginName] = plugin; newPluginMap[pluginName] = plugin;
@ -158,31 +124,20 @@ void Device::reloadPlugins()
} }
} }
const bool differentPlugins = m_plugins != newPluginMap;
//Erase all left plugins in the original map (meaning that we don't want //Erase all left plugins in the original map (meaning that we don't want
//them anymore, otherwise they would have been moved to the newPluginMap) //them anymore, otherwise they would have been moved to the newPluginMap)
const QStringList newSupportedIncomingInterfaces = supportedIncomingInterfaces.toList();
const QStringList newSupportedOutgoingInterfaces = supportedOutgoingInterfaces.toList();
const bool capabilitiesChanged = (m_pluginsByOutgoingInterface != newPluginsByOutgoingInterface
|| m_supportedIncomingInterfaces != newSupportedIncomingInterfaces);
qDeleteAll(m_plugins); qDeleteAll(m_plugins);
m_plugins = newPluginMap; m_plugins = newPluginMap;
m_supportedIncomingInterfaces = newSupportedIncomingInterfaces; m_pluginsByIncomingCapability = newPluginsByIncomingCapability;
m_supportedOutgoingInterfaces = newSupportedOutgoingInterfaces;
m_pluginsByOutgoingInterface = newPluginsByOutgoingInterface;
m_pluginsByIncomingInterface = newPluginsByIncomingInterface;
m_unsupportedPlugins = unsupportedPlugins;
//TODO: see how it works in Android (only done once, when created)
Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) { Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) {
plugin->connected(); plugin->connected();
} }
if (differentPlugins) {
Q_EMIT pluginsChanged(); Q_EMIT pluginsChanged();
if (capabilitiesChanged && isReachable() && isTrusted())
{
NetworkPackage np(PACKAGE_TYPE_CAPABILITIES);
np.set<QStringList>("IncomingCapabilities", newSupportedIncomingInterfaces);
np.set<QStringList>("OutgoingCapabilities", newSupportedOutgoingInterfaces);
sendPackage(np);
} }
} }
@ -271,13 +226,21 @@ void Device::addLink(const NetworkPackage& identityPackage, DeviceLink* link)
qSort(m_deviceLinks.begin(), m_deviceLinks.end(), lessThan); qSort(m_deviceLinks.begin(), m_deviceLinks.end(), lessThan);
if (m_deviceLinks.size() == 1) { const bool capabilitiesSupported = identityPackage.has("incomingCapabilities") || identityPackage.has("outgoingCapabilities");
reloadPlugins(); //Will load the plugins if (capabilitiesSupported) {
Q_EMIT reachableStatusChanged(); const QSet<QString> outgoingCapabilities = identityPackage.get<QStringList>("outgoingCapabilities").toSet()
, incomingCapabilities = identityPackage.get<QStringList>("incomingCapabilities").toSet();
m_supportedPlugins = PluginLoader::instance()->pluginsForCapabilities(incomingCapabilities, outgoingCapabilities);
qDebug() << "new plugins for" << m_deviceName << m_supportedPlugins << incomingCapabilities << outgoingCapabilities;
} else { } else {
Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) { m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet();
plugin->connected();
} }
reloadPlugins();
if (m_deviceLinks.size() == 1) {
Q_EMIT reachableStatusChanged();
} }
connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged); connect(link, &DeviceLink::pairStatusChanged, this, &Device::pairStatusChanged);
@ -296,6 +259,7 @@ void Device::removeLink(DeviceLink* link)
//qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining"; //qCDebug(KDECONNECT_CORE) << "RemoveLink" << m_deviceLinks.size() << "links remaining";
if (m_deviceLinks.isEmpty()) { if (m_deviceLinks.isEmpty()) {
m_supportedPlugins.clear();
reloadPlugins(); reloadPlugins();
Q_EMIT reachableStatusChanged(); Q_EMIT reachableStatusChanged();
} }
@ -317,17 +281,8 @@ bool Device::sendPackage(NetworkPackage& np)
void Device::privateReceivedPackage(const NetworkPackage& np) void Device::privateReceivedPackage(const NetworkPackage& np)
{ {
Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR); Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR);
if (np.type() == PACKAGE_TYPE_CAPABILITIES) { if (isTrusted()) {
QSet<QString> newIncomingCapabilities = np.get<QStringList>("IncomingCapabilities", QStringList()).toSet(); const QList<KdeConnectPlugin*> plugins = m_pluginsByIncomingCapability.values(np.type());
QSet<QString> newOutgoingCapabilities = np.get<QStringList>("OutgoingCapabilities", QStringList()).toSet();
if (newOutgoingCapabilities != m_outgoingCapabilities || newIncomingCapabilities != m_incomingCapabilities) {
m_incomingCapabilities = newIncomingCapabilities;
m_outgoingCapabilities = newOutgoingCapabilities;
reloadPlugins();
}
} else if (isTrusted()) {
const QList<KdeConnectPlugin*> plugins = m_pluginsByIncomingInterface.values(np.type());
if (plugins.isEmpty()) { if (plugins.isEmpty()) {
qWarning() << "discarding unsupported package" << np.type() << "for" << name(); qWarning() << "discarding unsupported package" << np.type() << "for" << name();
} }

View file

@ -43,7 +43,7 @@ class KDECONNECTCORE_EXPORT Device
Q_PROPERTY(QString statusIconName READ statusIconName) Q_PROPERTY(QString statusIconName READ statusIconName)
Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableStatusChanged) Q_PROPERTY(bool isReachable READ isReachable NOTIFY reachableStatusChanged)
Q_PROPERTY(bool isTrusted READ isTrusted NOTIFY trustedChanged) Q_PROPERTY(bool isTrusted READ isTrusted NOTIFY trustedChanged)
Q_PROPERTY(QStringList unsupportedPlugins READ unsupportedPlugins NOTIFY pluginsChanged) Q_PROPERTY(QStringList supportedPlugins READ supportedPlugins NOTIFY pluginsChanged)
public: public:
@ -82,7 +82,6 @@ public:
QString type() const { return type2str(m_deviceType); } QString type() const { return type2str(m_deviceType); }
QString iconName() const; QString iconName() const;
QString statusIconName() const; QString statusIconName() const;
QStringList unsupportedPlugins() const { return m_unsupportedPlugins; }
Q_SCRIPTABLE QString encryptionInfo() const; Q_SCRIPTABLE QString encryptionInfo() const;
//Add and remove links //Add and remove links
@ -106,6 +105,7 @@ public:
void cleanUnneededLinks(); void cleanUnneededLinks();
int protocolVersion() { return m_protocolVersion; } int protocolVersion() { return m_protocolVersion; }
QStringList supportedPlugins() const { return m_supportedPlugins.toList(); }
public Q_SLOTS: public Q_SLOTS:
///sends a @p np package to the device ///sends a @p np package to the device
@ -147,13 +147,8 @@ private: //Fields (TODO: dPointer!)
QHash<QString, KdeConnectPlugin*> m_plugins; QHash<QString, KdeConnectPlugin*> m_plugins;
//Capabilities stuff //Capabilities stuff
QMultiMap<QString, KdeConnectPlugin*> m_pluginsByIncomingInterface; QMultiMap<QString, KdeConnectPlugin*> m_pluginsByIncomingCapability;
QMultiMap<QString, KdeConnectPlugin*> m_pluginsByOutgoingInterface; QSet<QString> m_supportedPlugins;
QSet<QString> m_incomingCapabilities;
QSet<QString> m_outgoingCapabilities;
QStringList m_supportedIncomingInterfaces;
QStringList m_supportedOutgoingInterfaces;
QStringList m_unsupportedPlugins;
}; };
Q_DECLARE_METATYPE(Device*) Q_DECLARE_METATYPE(Device*)

View file

@ -67,6 +67,8 @@ void NetworkPackage::createIdentityPackage(NetworkPackage* np)
np->set("deviceName", config->name()); np->set("deviceName", config->name());
np->set("deviceType", config->deviceType()); np->set("deviceType", config->deviceType());
np->set("protocolVersion", NetworkPackage::ProtocolVersion); np->set("protocolVersion", NetworkPackage::ProtocolVersion);
np->set("incomingCapabilities", PluginLoader::instance()->incomingCapabilities());
np->set("outgoingCapabilities", PluginLoader::instance()->outgoingCapabilities());
//qCDebug(KDECONNECT_CORE) << "createIdentityPackage" << np->serialize(); //qCDebug(KDECONNECT_CORE) << "createIdentityPackage" << np->serialize();
} }

View file

@ -23,6 +23,5 @@
#define PACKAGE_TYPE_IDENTITY QLatin1String("kdeconnect.identity") #define PACKAGE_TYPE_IDENTITY QLatin1String("kdeconnect.identity")
#define PACKAGE_TYPE_PAIR QLatin1String("kdeconnect.pair") #define PACKAGE_TYPE_PAIR QLatin1String("kdeconnect.pair")
#define PACKAGE_TYPE_CAPABILITIES QLatin1String("kdeconnect.capabilities")
#endif // NETWORKPACKAGETYPES_H #endif // NETWORKPACKAGETYPES_H

View file

@ -83,7 +83,7 @@ KdeConnectPlugin* PluginLoader::instantiatePluginForDevice(const QString& plugin
return ret; return ret;
} }
QStringList PluginLoader::incomingInterfaces() const QStringList PluginLoader::incomingCapabilities() const
{ {
QSet<QString> ret; QSet<QString> ret;
Q_FOREACH (const KPluginMetaData& service, plugins) { Q_FOREACH (const KPluginMetaData& service, plugins) {
@ -92,7 +92,7 @@ QStringList PluginLoader::incomingInterfaces() const
return ret.toList(); return ret.toList();
} }
QStringList PluginLoader::outgoingInterfaces() const QStringList PluginLoader::outgoingCapabilities() const
{ {
QSet<QString> ret; QSet<QString> ret;
Q_FOREACH (const KPluginMetaData& service, plugins) { Q_FOREACH (const KPluginMetaData& service, plugins) {
@ -100,3 +100,22 @@ QStringList PluginLoader::outgoingInterfaces() const
} }
return ret.toList(); return ret.toList();
} }
QSet<QString> PluginLoader::pluginsForCapabilities(const QSet<QString>& incoming, const QSet<QString>& outgoing)
{
QSet<QString> ret;
Q_FOREACH (const KPluginMetaData& service, plugins) {
const QSet<QString> pluginIncomingCapabilities = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType").toSet();
const QSet<QString> pluginOutgoingCapabilities = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-OutgoingPackageType").toSet();
if ((pluginIncomingCapabilities.isEmpty() && pluginOutgoingCapabilities.isEmpty())
|| incoming.intersects(pluginOutgoingCapabilities) || outgoing.intersects(pluginIncomingCapabilities)) {
ret += service.pluginId();
} else {
qDebug() << "discarding..." << service.pluginId();
}
}
return ret;
}

View file

@ -36,12 +36,14 @@ class PluginLoader
public: public:
static PluginLoader* instance(); static PluginLoader* instance();
QStringList incomingInterfaces() const;
QStringList outgoingInterfaces() const;
QStringList getPluginList() const; QStringList getPluginList() const;
KPluginMetaData getPluginInfo(const QString& name) const; KPluginMetaData getPluginInfo(const QString& name) const;
KdeConnectPlugin* instantiatePluginForDevice(const QString& name, Device* device) const; KdeConnectPlugin* instantiatePluginForDevice(const QString& name, Device* device) const;
QStringList incomingCapabilities() const;
QStringList outgoingCapabilities() const;
QSet<QString> pluginsForCapabilities(const QSet<QString> &incoming, const QSet<QString> &outgoing);
private: private:
PluginLoader(); PluginLoader();
QHash<QString, KPluginMetaData> plugins; QHash<QString, KPluginMetaData> plugins;

View file

@ -199,9 +199,9 @@ void KdeConnectKcm::deviceSelected(const QModelIndex& current)
void KdeConnectKcm::resetCurrentDevice() void KdeConnectKcm::resetCurrentDevice()
{ {
const QStringList unsupportedPluginNames = currentDevice->unsupportedPlugins(); const QStringList supportedPluginNames = currentDevice->supportedPlugins();
if (m_oldUnsupportedPluginNames != unsupportedPluginNames) { if (m_oldSupportedPluginNames != supportedPluginNames) {
resetDeviceView(); resetDeviceView();
} }
} }
@ -222,12 +222,12 @@ void KdeConnectKcm::resetDeviceView()
QList<KPluginInfo> availablePluginInfo; QList<KPluginInfo> availablePluginInfo;
QList<KPluginInfo> unsupportedPluginInfo; QList<KPluginInfo> unsupportedPluginInfo;
m_oldUnsupportedPluginNames = currentDevice->unsupportedPlugins(); m_oldSupportedPluginNames = currentDevice->supportedPlugins();
for (auto it = pluginInfo.cbegin(), itEnd = pluginInfo.cend(); it!=itEnd; ++it) { for (auto it = pluginInfo.cbegin(), itEnd = pluginInfo.cend(); it!=itEnd; ++it) {
if (m_oldUnsupportedPluginNames.contains(it->pluginName())) { if (m_oldSupportedPluginNames.contains(it->pluginName())) {
unsupportedPluginInfo.append(*it);
} else {
availablePluginInfo.append(*it); availablePluginInfo.append(*it);
} else {
unsupportedPluginInfo.append(*it);
} }
} }

View file

@ -70,7 +70,7 @@ private:
DevicesSortProxyModel* sortProxyModel; DevicesSortProxyModel* sortProxyModel;
DeviceDbusInterface* currentDevice; DeviceDbusInterface* currentDevice;
QModelIndex currentIndex; QModelIndex currentIndex;
QStringList m_oldUnsupportedPluginNames; QStringList m_oldSupportedPluginNames;
public Q_SLOTS: public Q_SLOTS:
void unpair(); void unpair();

View file

@ -68,11 +68,11 @@ class PluginLoadTest : public QObject
d->setPluginEnabled("kdeconnect_mousepad", false); d->setPluginEnabled("kdeconnect_mousepad", false);
QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), false); QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), false);
QVERIFY(d->unsupportedPlugins().contains("kdeconnect_remotecontrol")); QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol"));
d->setPluginEnabled("kdeconnect_mousepad", true); d->setPluginEnabled("kdeconnect_mousepad", true);
QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), true); QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), true);
QVERIFY(!d->unsupportedPlugins().contains("kdeconnect_remotecontrol")); QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol"));
} }
private: private: