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 "daemon.h"
#define MIN_VERSION_WITH_CAPPABILITIES_SUPPORT 6
Q_LOGGING_CATEGORY(KDECONNECT_CORE, "kdeconnect.core")
static void warn(const QString &info)
@ -69,8 +67,6 @@ Device::Device(QObject* parent, const NetworkPackage& identityPackage, DeviceLin
: QObject(parent)
, m_deviceId(identityPackage.get<QString>("deviceId"))
, m_deviceName(identityPackage.get<QString>("deviceName"))
, m_deviceType(str2type(identityPackage.get<QString>("deviceType")))
, m_protocolVersion(identityPackage.get<int>("protocolVersion", -1))
{
addLink(identityPackage, dl);
@ -99,45 +95,17 @@ QStringList Device::loadedPlugins() const
void Device::reloadPlugins()
{
QHash<QString, KdeConnectPlugin*> newPluginMap;
QMultiMap<QString, KdeConnectPlugin*> newPluginsByIncomingInterface;
QMultiMap<QString, KdeConnectPlugin*> newPluginsByOutgoingInterface;
QSet<QString> supportedIncomingInterfaces;
QSet<QString> supportedOutgoingInterfaces;
QStringList unsupportedPlugins;
QMultiMap<QString, KdeConnectPlugin*> newPluginsByIncomingCapability;
if (isTrusted() && isReachable()) { //Do not load any plugin for unpaired devices, nor useless loading them for unreachable devices
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 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);
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;
}
const QSet<QString> incomingCapabilities = KPluginMetaData::readStringList(service.rawData(), "X-KdeConnect-SupportedPackageType").toSet();
if (pluginEnabled) {
KdeConnectPlugin* plugin = m_plugins.take(pluginName);
@ -145,12 +113,10 @@ void Device::reloadPlugins()
if (!plugin) {
plugin = loader->instantiatePluginForDevice(pluginName, this);
}
Q_ASSERT(plugin);
Q_FOREACH (const QString& interface, incomingInterfaces) {
newPluginsByIncomingInterface.insert(interface, plugin);
}
Q_FOREACH (const QString& interface, outgoingInterfaces) {
newPluginsByOutgoingInterface.insert(interface, plugin);
Q_FOREACH (const QString& interface, incomingCapabilities) {
newPluginsByIncomingCapability.insert(interface, 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
//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);
m_plugins = newPluginMap;
m_supportedIncomingInterfaces = newSupportedIncomingInterfaces;
m_supportedOutgoingInterfaces = newSupportedOutgoingInterfaces;
m_pluginsByOutgoingInterface = newPluginsByOutgoingInterface;
m_pluginsByIncomingInterface = newPluginsByIncomingInterface;
m_unsupportedPlugins = unsupportedPlugins;
m_pluginsByIncomingCapability = newPluginsByIncomingCapability;
//TODO: see how it works in Android (only done once, when created)
Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) {
plugin->connected();
}
if (differentPlugins) {
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);
if (m_deviceLinks.size() == 1) {
reloadPlugins(); //Will load the plugins
Q_EMIT reachableStatusChanged();
const bool capabilitiesSupported = identityPackage.has("incomingCapabilities") || identityPackage.has("outgoingCapabilities");
if (capabilitiesSupported) {
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 {
Q_FOREACH(KdeConnectPlugin* plugin, m_plugins) {
plugin->connected();
m_supportedPlugins = PluginLoader::instance()->getPluginList().toSet();
}
reloadPlugins();
if (m_deviceLinks.size() == 1) {
Q_EMIT reachableStatusChanged();
}
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";
if (m_deviceLinks.isEmpty()) {
m_supportedPlugins.clear();
reloadPlugins();
Q_EMIT reachableStatusChanged();
}
@ -317,17 +281,8 @@ bool Device::sendPackage(NetworkPackage& np)
void Device::privateReceivedPackage(const NetworkPackage& np)
{
Q_ASSERT(np.type() != PACKAGE_TYPE_PAIR);
if (np.type() == PACKAGE_TYPE_CAPABILITIES) {
QSet<QString> newIncomingCapabilities = np.get<QStringList>("IncomingCapabilities", QStringList()).toSet();
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 (isTrusted()) {
const QList<KdeConnectPlugin*> plugins = m_pluginsByIncomingCapability.values(np.type());
if (plugins.isEmpty()) {
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(bool isReachable READ isReachable NOTIFY reachableStatusChanged)
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:
@ -82,7 +82,6 @@ public:
QString type() const { return type2str(m_deviceType); }
QString iconName() const;
QString statusIconName() const;
QStringList unsupportedPlugins() const { return m_unsupportedPlugins; }
Q_SCRIPTABLE QString encryptionInfo() const;
//Add and remove links
@ -106,6 +105,7 @@ public:
void cleanUnneededLinks();
int protocolVersion() { return m_protocolVersion; }
QStringList supportedPlugins() const { return m_supportedPlugins.toList(); }
public Q_SLOTS:
///sends a @p np package to the device
@ -147,13 +147,8 @@ private: //Fields (TODO: dPointer!)
QHash<QString, KdeConnectPlugin*> m_plugins;
//Capabilities stuff
QMultiMap<QString, KdeConnectPlugin*> m_pluginsByIncomingInterface;
QMultiMap<QString, KdeConnectPlugin*> m_pluginsByOutgoingInterface;
QSet<QString> m_incomingCapabilities;
QSet<QString> m_outgoingCapabilities;
QStringList m_supportedIncomingInterfaces;
QStringList m_supportedOutgoingInterfaces;
QStringList m_unsupportedPlugins;
QMultiMap<QString, KdeConnectPlugin*> m_pluginsByIncomingCapability;
QSet<QString> m_supportedPlugins;
};
Q_DECLARE_METATYPE(Device*)

View file

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

View file

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

View file

@ -83,7 +83,7 @@ KdeConnectPlugin* PluginLoader::instantiatePluginForDevice(const QString& plugin
return ret;
}
QStringList PluginLoader::incomingInterfaces() const
QStringList PluginLoader::incomingCapabilities() const
{
QSet<QString> ret;
Q_FOREACH (const KPluginMetaData& service, plugins) {
@ -92,7 +92,7 @@ QStringList PluginLoader::incomingInterfaces() const
return ret.toList();
}
QStringList PluginLoader::outgoingInterfaces() const
QStringList PluginLoader::outgoingCapabilities() const
{
QSet<QString> ret;
Q_FOREACH (const KPluginMetaData& service, plugins) {
@ -100,3 +100,22 @@ QStringList PluginLoader::outgoingInterfaces() const
}
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:
static PluginLoader* instance();
QStringList incomingInterfaces() const;
QStringList outgoingInterfaces() const;
QStringList getPluginList() const;
KPluginMetaData getPluginInfo(const QString& name) 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:
PluginLoader();
QHash<QString, KPluginMetaData> plugins;

View file

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

View file

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

View file

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