Commit 88fab1f3 authored by Albert Vaca Cintora's avatar Albert Vaca Cintora

Plugins are now owned by devices and not by daemon

Plugins can no longer emit networkpackages for *every* device.
Plugins are stored in device, wich selectively loads them.
A Device is needed in order to instantiate a Plugin (via PluginLoader)
PluginLoader is a singleton, because every instance of Device need it.
Added KPluginSelector in the KCM to select the plugins to load.
Added architecture explanation to README

Only PingPlugin is working by now.
parent 9b7eecc6
The kdeconnect protocol:
Class diagram
==============
Backend_1 ... Backend_N
\ | /
Daemon
/ | \
Device_1 ... Device_N
/ \
|-Plugin_1 |-DeviceLink_1
|-Plugin_2 |-DeviceLink_2
|- ... |-...
|-Plugin_N |-DeviceLink_N
Daemon instantiates Backends
Backends manage to create DeviceLinks with the devices they can reach, and Q_EMIT them to Daemon.
When Daemon receives a DeviceLink from a backend it:
- If he already knows the Device, adds the DeviceLink to the Device
- If not, it creates a new Device.
Devices contain a list of DeviceLinks, plus a list of Plugins (instantiated automatically)
Information for and from Plugins is encapsulated in NetworkPackages.
When a DeviceLink receives a NetworkPackage from the device in the other end, Device will notify all the plugins.
When a Plugin wants to send a NetworkPackage, it does so using the pointer to Device
The NetworkPackage format
=========================
Communication between heterogenous devices is achieved using NetworkPackages.
NetworkPackages are independent and self-contained pieces of information that
are sent from one device to another serialized in json.
are sent from one device to another (via a DeviceLink) serialized in json.
The basic structure of a NetworkPackage is the following:
......@@ -16,5 +49,6 @@ The basic structure of a NetworkPackage is the following:
}
Each type of package defines what it should contain inside its "body", so only
the sender and receiver of this type of package need agree about it.
the emisor Plugin and receiver Plugin of this type of package need agree about
its content.
......@@ -57,13 +57,7 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
//Debugging
qDebug() << "Starting KdeConnect daemon";
//Load plugins
PluginLoader *loader = new PluginLoader(this);
connect(loader, SIGNAL(pluginLoaded(PackageInterface*)), this, SLOT(pluginLoaded(PackageInterface*)));
loader->loadAllPlugins();
//Load backends (hardcoded by now)
//use: https://techbase.kde.org/Development/Tutorials/Services/Plugins
//Load backends (hardcoded by now, should be plugins in a future)
mLinkProviders.insert(new BroadcastTcpLinkProvider());
//mLinkProviders.insert(new AvahiTcpLinkProvider());
//mLinkProviders.insert(new LoopbackLinkProvider());
......@@ -77,12 +71,6 @@ Daemon::Daemon(QObject *parent, const QList<QVariant>&)
const QString& name = data.readEntry<QString>("name", defaultName);
Device* device = new Device(id, name);
mDevices[id] = device;
Q_FOREACH (PackageInterface* pr, mPackageInterfaces) {
connect(device, SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
pr, SLOT(receivePackage(const Device&, const NetworkPackage&)));
connect(pr, SIGNAL(sendPackage(const NetworkPackage&)),
device, SLOT(sendPackage(const NetworkPackage&)));
}
}
QNetworkSession* network = new QNetworkSession(QNetworkConfigurationManager().defaultConfiguration());
......@@ -123,19 +111,6 @@ QStringList Daemon::devices()
return mDevices.keys();
}
void Daemon::pluginLoaded(PackageInterface* packageInterface)
{
qDebug() << "PLUUUUUUUUUUUUUUUUGINLOADEEEEEEEEEEEEEEEEEEEEEEED";
mPackageInterfaces.append(packageInterface);
Q_FOREACH(Device* device, mDevices) {
connect(device, SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
packageInterface, SLOT(receivePackage(const Device&, const NetworkPackage&)));
connect(packageInterface, SIGNAL(sendPackage(const NetworkPackage&)),
device, SLOT(sendPackage(const NetworkPackage&)));
}
}
void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl)
{
const QString& id = identityPackage.get<QString>("deviceId");
......@@ -166,10 +141,7 @@ void Daemon::onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink*
Device* device = new Device(id, name, dl);
mDevices[id] = device;
Q_FOREACH (PackageInterface* pr, mPackageInterfaces) {
connect(device, SIGNAL(receivedPackage(const Device&, const NetworkPackage&)),
pr, SLOT(receivePackage(const Device&, const NetworkPackage&)));
}
Q_EMIT newDeviceAdded(id);
}
......
......@@ -72,19 +72,14 @@ Q_SIGNALS:
private Q_SLOTS:
void onNewDeviceLink(const NetworkPackage& identityPackage, DeviceLink* dl);
void pluginLoaded(PackageInterface*);
private:
//Every known device
QMap<QString, Device*> mDevices;
//Different ways to find devices and connect to them
QSet<LinkProvider*> mLinkProviders;
//The classes that send and receive the packages
QVector<PackageInterface*> mPackageInterfaces;
//Every known device
QMap<QString, Device*> mDevices;
};
......
......@@ -3,8 +3,14 @@
#include <KSharedPtr>
#include <KSharedConfig>
#include <KConfigGroup>
#include <KStandardDirs>
#include <KPluginSelector>
#include <KServiceTypeTrader>
#include <KPluginInfo>
#include <QDebug>
#include "plugins/pluginloader.h"
#include "devicelinks/devicelink.h"
#include "linkproviders/linkprovider.h"
#include "networkpackage.h"
......@@ -19,6 +25,7 @@ Device::Device(const QString& id, const QString& name)
//Register in bus
QDBusConnection::sessionBus().registerObject("/modules/kdeconnect/devices/"+id, this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
reloadPlugins();
}
Device::Device(const QString& id, const QString& name, DeviceLink* link)
......@@ -32,6 +39,8 @@ Device::Device(const QString& id, const QString& name, DeviceLink* link)
QDBusConnection::sessionBus().registerObject("/modules/kdeconnect/devices/"+id, this, QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAdaptors);
addLink(link);
reloadPlugins();
}
/*
Device::Device(const QString& id, const QString& name, DeviceLink* link)
......@@ -50,6 +59,43 @@ Device::Device(const QString& id, const QString& name, DeviceLink* link)
QDBusConnection::sessionBus().registerObject("/modules/kdeconnect/Devices/"+id, this);
}
*/
void Device::reloadPlugins()
{
qDeleteAll(m_plugins);
m_plugins.clear();
QString path = KStandardDirs().resourceDirs("config").first()+"kdeconnect/";
QMap<QString,QString> pluginStates = KSharedConfig::openConfig(path + id())->group("Plugins").entryMap();
PluginLoader* loader = PluginLoader::instance();
//Code borrowed from KWin
foreach (const QString& pluginName, loader->getPluginList()) {
const QString value = pluginStates.value(pluginName + QString::fromLatin1("Enabled"), QString());
bool enabled = (value.isNull() ? true : QVariant(value).toBool()); //Enable all plugins by default
qDebug() << pluginName << "enabled:" << enabled;
if (enabled) {
PackageInterface* plugin = loader->instantiatePluginForDevice(pluginName, this);
connect(this, SIGNAL(receivedPackage(const NetworkPackage&)),
plugin, SLOT(receivePackage(const NetworkPackage&)));
// connect(packageInterface, SIGNAL(sendPackage(const NetworkPackage&)),
// device, SLOT(sendPackage(const NetworkPackage&)));
m_plugins.append(plugin);
}
}
}
void Device::setPair(bool b)
{
qDebug() << "setPair" << b;
......@@ -122,10 +168,10 @@ void Device::privateReceivedPackage(const NetworkPackage& np)
if (np.type() == "kdeconnect.identity" && !m_knownIdentiy) {
m_deviceName = np.get<QString>("deviceName");
} else if (m_paired) {
qDebug() << "package received from paired device";
emit receivedPackage(*this, np);
qDebug() << "package received from trusted device";
Q_EMIT receivedPackage(np);
} else {
qDebug() << "not paired, ignoring package";
qDebug() << "device" << name() << "not trusted, ignoring package" << np.type();
}
}
......
......@@ -28,6 +28,7 @@
#include "devicelinks/devicelink.h"
class DeviceLink;
class PackageInterface;
class Device
: public QObject
......@@ -38,7 +39,6 @@ class Device
Q_PROPERTY(QString name READ name)
public:
//Device known from KConfig, we trust it but we need to wait for a incoming devicelink to communicate
Device(const QString& id, const QString& name);
......@@ -56,19 +56,20 @@ public:
void addLink(DeviceLink*);
void removeLink(DeviceLink*);
Q_SCRIPTABLE QStringList availableLinks() const;
Q_SCRIPTABLE bool paired() const { return m_paired; }
Q_SCRIPTABLE bool reachable() const { return !m_deviceLinks.empty(); }
//Send and receive
Q_SIGNALS:
void receivedPackage(const Device& device, const NetworkPackage& np);
void receivedPackage(const NetworkPackage& np);
public Q_SLOTS:
bool sendPackage(const NetworkPackage& np) const;
//Public dbus operations
//Dbus operations called from kcm
public Q_SLOTS:
Q_SCRIPTABLE QStringList availableLinks() const;
Q_SCRIPTABLE bool paired() const { return m_paired; }
Q_SCRIPTABLE bool reachable() const { return !m_deviceLinks.empty(); }
Q_SCRIPTABLE void setPair(bool b);
Q_SCRIPTABLE void reloadPlugins();
Q_SCRIPTABLE void sendPing();
Q_SIGNALS:
......@@ -83,9 +84,12 @@ private:
QString m_deviceId;
QString m_deviceName;
QList<DeviceLink*> m_deviceLinks;
QList<PackageInterface*> m_plugins;
bool m_knownIdentiy;
};
Q_DECLARE_METATYPE(Device*)
#endif // DEVICE_H
install(FILES kdeconnect_package_interface.desktop DESTINATION ${SERVICETYPES_INSTALL_DIR})
add_subdirectory(ping)
......@@ -20,8 +20,17 @@
#include "packageinterface.h"
PackageInterface::PackageInterface(QObject* parent)
#include <QPointer>
#include "../device.h"
PackageInterface::PackageInterface(QObject* parent, const QVariantList& args)
: QObject(parent)
{
//gcc complains if we don't add something to compile on a class with virtual functions
mDevice = qvariant_cast< Device* >(args.first());
}
Device* PackageInterface::device()
{
return mDevice;
}
......@@ -22,6 +22,7 @@
#define PACKAGEINTERFACE_H
#include <QObject>
#include <QVariantList>
#include <kdemacros.h>
#include <KPluginFactory>
......@@ -40,17 +41,18 @@ class KDE_EXPORT PackageInterface
Q_OBJECT
public:
PackageInterface(QObject* parent = 0);
PackageInterface(QObject* parent, const QVariantList& args);
virtual ~PackageInterface() { }
Device* device();
public Q_SLOTS:
//Returns true if it has handled the package in some way
//device.sendPackage can be used to send an answer back to the device
virtual bool receivePackage(const Device& device, const NetworkPackage& np) = 0;
virtual bool receivePackage(const NetworkPackage& np) = 0;
private:
Device* mDevice;
Q_SIGNALS:
//Sends a package to *all* connected devices
void sendPackage(const NetworkPackage& np);
};
#endif
......@@ -8,11 +8,6 @@ X-KDE-PluginInfo-Email=albertvaka@gmail.com
X-KDE-PluginInfo-Name=kdeconnect_ping
X-KDE-PluginInfo-Version=0.1
X-KDE-PluginInfo-Website=http://albertvaka.wordpress.com
X-KDE-PluginInfo-Category=Network
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-KDE-ParentApp=kdeconnect
X-KDE-Version=4.0
Name=Ping
Comment=Send and receive pings
......@@ -20,19 +20,20 @@
#include "pingpackageinterface.h"
#include <KDebug>
#include <kicon.h>
#include <KNotification>
#include <KIcon>
#include <QDebug>
K_PLUGIN_FACTORY( KdeConnectPluginFactory, registerPlugin< PingPackageInterface >(); )
K_EXPORT_PLUGIN( KdeConnectPluginFactory("kdeconnect_ping", "kdeconnect_ping") )
PingPackageInterface::PingPackageInterface(QObject* parent, const QVariantList& args)
: PackageInterface(parent)
: PackageInterface(parent, args)
{
Q_UNUSED(args);
qDebug() << "Plugin constructor for device" << device()->name();
}
bool PingPackageInterface::receivePackage(const Device& device, const NetworkPackage& np)
bool PingPackageInterface::receivePackage(const NetworkPackage& np)
{
if (np.type() != PACKAGE_TYPE_PING) return false;
......@@ -41,7 +42,7 @@ bool PingPackageInterface::receivePackage(const Device& device, const NetworkPac
notification->setPixmap(KIcon("dialog-ok").pixmap(48, 48));
notification->setComponentData(KComponentData("kdeconnect", "kdeconnect"));
notification->setTitle("Ping!");
notification->setText(device.name());
notification->setText(device()->name());
notification->sendEvent();
return true;
......
......@@ -21,7 +21,7 @@
#ifndef PINGPACKAGEINTERFACE_H
#define PINGPACKAGEINTERFACE_H
#include <knotification.h>
#include <QObject>
#include "../packageinterface.h"
......@@ -32,8 +32,9 @@ class KDE_EXPORT PingPackageInterface
public:
explicit PingPackageInterface(QObject *parent, const QVariantList &args);
virtual bool receivePackage(const Device& device, const NetworkPackage& np);
public Q_SLOTS:
virtual bool receivePackage(const NetworkPackage& np);
};
......
......@@ -21,49 +21,58 @@
#include "pluginloader.h"
#include "packageinterface.h"
#include "plugins/ping/pingpackageinterface.h"
#include <KServiceTypeTrader>
#include <KDebug>
PluginLoader::PluginLoader(QObject * parent)
: QObject(parent)
{
}
#include "../device.h"
PluginLoader::~PluginLoader()
PluginLoader* PluginLoader::instance()
{
static PluginLoader* instance = new PluginLoader();
return instance;
}
void PluginLoader::loadAllPlugins()
PluginLoader::PluginLoader()
{
kDebug() << "Load all plugins";
KService::List offers = KServiceTypeTrader::self()->query("KdeConnect/Plugin");
for(KService::List::const_iterator iter = offers.begin(); iter < offers.end(); ++iter) {
KService::Ptr service = *iter;
plugins[service->library()] = service;
}
}
qDebug() << "LO TRAIGO DE OFERTA CHACHO" << offers;
QStringList PluginLoader::getPluginList()
{
return plugins.keys();
}
KService::List::const_iterator iter;
for(iter = offers.begin(); iter < offers.end(); ++iter)
{
QString error;
KService::Ptr service = *iter;
PackageInterface* PluginLoader::instantiatePluginForDevice(QString id, Device* device) {
KPluginFactory *factory = KPluginLoader(service->library()).factory();
KService::Ptr service = plugins[id];
if (!service) {
qDebug() << "Plugin unknown" << id;
return NULL;
}
if (!factory)
{
//KMessageBox::error(0, i18n("<html><p>KPluginFactory could not load the plugin:<br /><i>%1</i></p></html>",
// service->library()));
kError(5001) << "KPluginFactory could not load the plugin:" << service->library();
continue;
}
KPluginFactory *factory = KPluginLoader(service->library()).factory();
if (!factory) {
qDebug() << "KPluginFactory could not load the plugin:" << service->library();
return NULL;
}
PackageInterface *plugin = factory->create<PackageInterface>(this);
QVariant deviceVariant;
deviceVariant.setValue<Device*>(device);
if (plugin) {
kDebug() << "Load plugin:" << service->name();
emit pluginLoaded(plugin);
} else {
kDebug() << error;
}
//FIXME: create<PackageInterface> return NULL
QObject *plugin = factory->create<QObject>(device, QVariantList() << deviceVariant);
if (!plugin) {
qDebug() << "Error loading plugin";
return NULL;
}
}
\ No newline at end of file
qDebug() << "Loaded plugin:" << service->name();
return (PackageInterface*)plugin;
}
......@@ -22,21 +22,29 @@
#define PACKAGEINTERFACELOADER_H
#include <QObject>
#include <QMap>
#include <QString>
#include "packageinterface.h"
#include <KPluginFactory>
#include <KService>
class PluginLoader : public QObject
class Device;
class PluginLoader
{
Q_OBJECT
public:
PluginLoader(QObject * parent);
virtual ~PluginLoader();
void loadAllPlugins();
public:
static PluginLoader* instance();
PackageInterface* instantiatePluginForDevice(QString name, Device* device);
QStringList getPluginList();
private:
PluginLoader();
QMap<QString,KService::Ptr> plugins;
signals:
void pluginLoaded(PackageInterface * plugin);
};
#endif
......@@ -25,7 +25,7 @@ target_link_libraries(kcm_kdeconnect
${QT_QTCORE_LIBRARY}
${QT_QTGUI_LIBRARY}
${KDE4_KDEUI_LIBRARY}
${KDE4_KIO_LIBRARY}
${KDE4_KCMUTILS_LIBS}
)
add_dependencies(kcm_kdeconnect
......
......@@ -118,7 +118,6 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
}
qDebug() << index.row() << ">= " << m_deviceList.count() << (index.row() >= m_deviceList.count());
if (!index.isValid() || index.row() < 0 || index.row() >= m_deviceList.count()) {
return QVariant();
}
......
......@@ -32,6 +32,8 @@
#include <QDBusConnection>
#include <QDBusInterface>
#include <KServiceTypeTrader>
#include <KPluginInfo>
#include <KDebug>
#include <kpluginfactory.h>
#include <kstandarddirs.h>
......@@ -43,7 +45,8 @@ KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&)
: KCModule(KdeConnectKcmFactory::componentData(), parent)
, kcmUi(new Ui::KdeConnectKcmUi())
, pairedDevicesList(new DevicesModel(this))
, config(KSharedConfig::openConfig("kdeconnectrc"))
, currentDevice(0)
//, config(KSharedConfig::openConfig("kdeconnectrc"))
{
kcmUi->setupUi(this);
......@@ -52,6 +55,8 @@ KdeConnectKcm::KdeConnectKcm(QWidget *parent, const QVariantList&)
kcmUi->deviceInfo->setVisible(false);
setButtons(KCModule::NoAdditionalButton);
connect(kcmUi->deviceList, SIGNAL(pressed(QModelIndex)), this, SLOT(deviceSelected(QModelIndex)));
connect(kcmUi->ping_button, SIGNAL(pressed()), this, SLOT(sendPing()));
connect(kcmUi->trust_checkbox,SIGNAL(toggled(bool)), this, SLOT(trustedStateChanged(bool)));
......@@ -64,27 +69,63 @@ KdeConnectKcm::~KdeConnectKcm()
void KdeConnectKcm::deviceSelected(const QModelIndex& current)
{
//Store previous selection
pluginsConfigChanged();
//FIXME: KPluginSelector has no way to remove a list of plugins and load another, so we need to destroy and recreate it each time
delete kcmUi->pluginSelector;
kcmUi->pluginSelector = new KPluginSelector(this);
kcmUi->verticalLayout_2->addWidget(kcmUi->pluginSelector);
bool valid = current.isValid();
kcmUi->deviceInfo->setVisible(valid);
if (!valid) return;
selectedIndex = current;
bool paired = pairedDevicesList->getDevice(current)->paired();
kcmUi->trust_checkbox->setChecked(paired);
currentDevice = pairedDevicesList->getDevice(current);
kcmUi->deviceName->setText(currentDevice->name());
kcmUi->trust_checkbox->setChecked(currentDevice->paired());
KService::List offers = KServiceTypeTrader::self()->query("KdeConnect/Plugin");
QList<KPluginInfo> scriptinfos = KPluginInfo::fromServices(offers);
QString path = KStandardDirs().resourceDirs("config").first()+"kdeconnect/";
KSharedConfigPtr deviceConfig = KSharedConfig::openConfig(path + currentDevice->id());
kcmUi->pluginSelector->addPlugins(scriptinfos, KPluginSelector::ReadConfigFile, "Plugins", QString(), deviceConfig);
connect(kcmUi->pluginSelector, SIGNAL(changed(bool)), this, SLOT(pluginsConfigChanged()));
}
void KdeConnectKcm::trustedStateChanged(bool b)
{
if (!selectedIndex.isValid()) return;
DeviceDbusInterface* device = pairedDevicesList->getDevice(selectedIndex);
device->setPair(b);
pairedDevicesList->deviceStatusChanged(device->id());
if (!currentDevice) return;
currentDevice->setPair(b);
pairedDevicesList->deviceStatusChanged(currentDevice->id());
}
void KdeConnectKcm::pluginsConfigChanged()
{
//Store previous selection
if (!currentDevice) return;
DeviceDbusInterface* auxCurrentDevice = currentDevice; //HACK to avoid infinite recursion (for some reason calling save on pluginselector emits changed)
currentDevice = 0;
kcmUi->pluginSelector->save();
currentDevice = auxCurrentDevice;
currentDevice->reloadPlugins();
}
void KdeConnectKcm::save()
{
pluginsConfigChanged();
KCModule::save();
}
void KdeConnectKcm::sendPing()
{
if (!selectedIndex.isValid()) return;
pairedDevicesList->getDevice(selectedIndex)->sendPing();
if (!currentDevice) return;
currentDevice->sendPing();
}
......@@ -30,13 +30,13 @@
#include "wizard.h"
#include "devicesmodel.h"
</