diff --git a/core/daemon.cpp b/core/daemon.cpp index 2ecf1526..3d631801 100644 --- a/core/daemon.cpp +++ b/core/daemon.cpp @@ -1,323 +1,328 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "daemon.h" #include #include #include #include #include #include "core_debug.h" #include "kdeconnectconfig.h" #include "networkpacket.h" #include "notificationserverinfo.h" #ifdef KDECONNECT_BLUETOOTH #include "backends/bluetooth/bluetoothlinkprovider.h" #endif #include "backends/lan/lanlinkprovider.h" #include "backends/loopback/loopbacklinkprovider.h" #include "device.h" #include "backends/devicelink.h" #include "backends/linkprovider.h" //In older Qt released, qAsConst isnt available #include "qtcompat_p.h" static Daemon* s_instance = nullptr; struct DaemonPrivate { //Different ways to find devices and connect to them QSet m_linkProviders; //Every known device QMap m_devices; QSet m_discoveryModeAcquisitions; bool m_testMode; }; Daemon* Daemon::instance() { Q_ASSERT(s_instance != nullptr); return s_instance; } Daemon::Daemon(QObject* parent, bool testMode) : QObject(parent) , d(new DaemonPrivate) { Q_ASSERT(!s_instance); s_instance = this; d->m_testMode = testMode; // HACK init may call pure virtual functions from this class so it can't be called directly from the ctor QTimer::singleShot(0, this, &Daemon::init); } void Daemon::init() { qCDebug(KDECONNECT_CORE) << "Daemon starting"; //Load backends if (d->m_testMode) d->m_linkProviders.insert(new LoopbackLinkProvider()); else { d->m_linkProviders.insert(new LanLinkProvider()); #ifdef KDECONNECT_BLUETOOTH d->m_linkProviders.insert(new BluetoothLinkProvider()); #endif #ifdef KDECONNECT_LOOPBACK d->m_linkProviders.insert(new LoopbackLinkProvider()); #endif } //Read remembered paired devices const QStringList& list = KdeConnectConfig::instance()->trustedDevices(); for (const QString& id : list) { addDevice(new Device(this, id)); } //Listen to new devices for (LinkProvider* a : qAsConst(d->m_linkProviders)) { connect(a, &LinkProvider::onConnectionReceived, this, &Daemon::onNewDeviceLink); a->onStart(); } //Register on DBus qDBusRegisterMetaType< QMap >(); QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.kdeconnect")); QDBusConnection::sessionBus().registerObject(QStringLiteral("/modules/kdeconnect"), this, QDBusConnection::ExportScriptableContents); NotificationServerInfo::instance().init(); qCDebug(KDECONNECT_CORE) << "Daemon started"; } void Daemon::acquireDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.insert(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { forceOnNetworkChange(); } } void Daemon::releaseDiscoveryMode(const QString& key) { bool oldState = d->m_discoveryModeAcquisitions.isEmpty(); d->m_discoveryModeAcquisitions.remove(key); if (oldState != d->m_discoveryModeAcquisitions.isEmpty()) { cleanDevices(); } } void Daemon::removeDevice(Device* device) { d->m_devices.remove(device->id()); device->deleteLater(); Q_EMIT deviceRemoved(device->id()); Q_EMIT deviceListChanged(); } void Daemon::cleanDevices() { const auto devs = d->m_devices; for (Device* device : devs) { if (device->isTrusted()) { continue; } device->cleanUnneededLinks(); //If there are no links remaining if (!device->isReachable()) { removeDevice(device); } } } void Daemon::forceOnNetworkChange() { qCDebug(KDECONNECT_CORE) << "Sending onNetworkChange to" << d->m_linkProviders.size() << "LinkProviders"; for (LinkProvider* a : qAsConst(d->m_linkProviders)) { a->onNetworkChange(); } } Device*Daemon::getDevice(const QString& deviceId) { for (Device* device : qAsConst(d->m_devices)) { if (device->id() == deviceId) { return device; } } return nullptr; } +const QSet& Daemon::getLinkProviders() const +{ + return d->m_linkProviders; +} + QStringList Daemon::devices(bool onlyReachable, bool onlyTrusted) const { QStringList ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret.append(device->id()); } return ret; } QMap Daemon::deviceNames(bool onlyReachable, bool onlyTrusted) const { QMap ret; for (Device* device : qAsConst(d->m_devices)) { if (onlyReachable && !device->isReachable()) continue; if (onlyTrusted && !device->isTrusted()) continue; ret[device->id()] = device->name(); } return ret; } void Daemon::onNewDeviceLink(const NetworkPacket& identityPacket, DeviceLink* dl) { const QString& id = identityPacket.get(QStringLiteral("deviceId")); //qCDebug(KDECONNECT_CORE) << "Device discovered" << id << "via" << dl->provider()->name(); if (d->m_devices.contains(id)) { qCDebug(KDECONNECT_CORE) << "It is a known device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = d->m_devices[id]; bool wasReachable = device->isReachable(); device->addLink(identityPacket, dl); if (!wasReachable) { Q_EMIT deviceVisibilityChanged(id, true); Q_EMIT deviceListChanged(); } } else { qCDebug(KDECONNECT_CORE) << "It is a new device" << identityPacket.get(QStringLiteral("deviceName")); Device* device = new Device(this, identityPacket, dl); //we discard the connections that we created but it's not paired. if (!isDiscoveringDevices() && !device->isTrusted() && !dl->linkShouldBeKeptAlive()) { device->deleteLater(); } else { addDevice(device); } } } void Daemon::onDeviceStatusChanged() { Device* device = (Device*)sender(); //qCDebug(KDECONNECT_CORE) << "Device" << device->name() << "status changed. Reachable:" << device->isReachable() << ". Paired: " << device->isPaired(); if (!device->isReachable() && !device->isTrusted()) { //qCDebug(KDECONNECT_CORE) << "Destroying device" << device->name(); removeDevice(device); } else { Q_EMIT deviceVisibilityChanged(device->id(), device->isReachable()); Q_EMIT deviceListChanged(); } } void Daemon::setAnnouncedName(const QString& name) { qCDebug(KDECONNECT_CORE()) << "Announcing name"; KdeConnectConfig::instance()->setName(name); forceOnNetworkChange(); Q_EMIT announcedNameChanged(name); } QString Daemon::announcedName() { return KdeConnectConfig::instance()->name(); } QNetworkAccessManager* Daemon::networkAccessManager() { static QPointer manager; if (!manager) { manager = new QNetworkAccessManager(this); } return manager; } QList Daemon::devicesList() const { return d->m_devices.values(); } bool Daemon::isDiscoveringDevices() const { return !d->m_discoveryModeAcquisitions.isEmpty(); } QString Daemon::deviceIdByName(const QString& name) const { for (Device* device : qAsConst(d->m_devices)) { if (device->name() == name && device->isTrusted()) return device->id(); } return {}; } void Daemon::addDevice(Device* device) { const QString id = device->id(); connect(device, &Device::reachableChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::trustedChanged, this, &Daemon::onDeviceStatusChanged); connect(device, &Device::hasPairingRequestsChanged, this, &Daemon::pairingRequestsChanged); connect(device, &Device::hasPairingRequestsChanged, this, [this, device](bool hasPairingRequests) { if (hasPairingRequests) askPairingConfirmation(device); } ); d->m_devices[id] = device; Q_EMIT deviceAdded(id); Q_EMIT deviceListChanged(); } QStringList Daemon::pairingRequests() const { QStringList ret; for(Device* dev: qAsConst(d->m_devices)) { if (dev->hasPairingRequests()) ret += dev->id(); } return ret; } Daemon::~Daemon() { } QString Daemon::selfId() const { return KdeConnectConfig::instance()->deviceId(); } diff --git a/core/daemon.h b/core/daemon.h index c498d3cb..83b3fa5d 100644 --- a/core/daemon.h +++ b/core/daemon.h @@ -1,105 +1,107 @@ /** * Copyright 2013 Albert Vaca * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KDECONNECT_DAEMON_H #define KDECONNECT_DAEMON_H #include #include #include #include "kdeconnectcore_export.h" #include "device.h" class NetworkPacket; class DeviceLink; class Device; class QNetworkAccessManager; class KDECONNECTCORE_EXPORT Daemon : public QObject { Q_OBJECT Q_CLASSINFO("D-Bus Interface", "org.kde.kdeconnect.daemon") Q_PROPERTY(bool isDiscoveringDevices READ isDiscoveringDevices) Q_PROPERTY(QStringList pairingRequests READ pairingRequests NOTIFY pairingRequestsChanged) public: explicit Daemon(QObject* parent, bool testMode = false); ~Daemon() override; static Daemon* instance(); QList devicesList() const; virtual void askPairingConfirmation(Device* device) = 0; virtual void reportError(const QString& title, const QString& description) = 0; virtual void quit() = 0; virtual QNetworkAccessManager* networkAccessManager(); Device* getDevice(const QString& deviceId); + const QSet& getLinkProviders() const; + QStringList pairingRequests() const; Q_SCRIPTABLE QString selfId() const; public Q_SLOTS: Q_SCRIPTABLE void acquireDiscoveryMode(const QString& id); Q_SCRIPTABLE void releaseDiscoveryMode(const QString& id); Q_SCRIPTABLE void forceOnNetworkChange(); ///don't try to turn into Q_PROPERTY, it doesn't work Q_SCRIPTABLE QString announcedName(); Q_SCRIPTABLE void setAnnouncedName(const QString& name); //Returns a list of ids. The respective devices can be manipulated using the dbus path: "/modules/kdeconnect/Devices/"+id Q_SCRIPTABLE QStringList devices(bool onlyReachable = false, bool onlyPaired = false) const; Q_SCRIPTABLE QMap deviceNames(bool onlyReachable = false, bool onlyPaired = false) const; Q_SCRIPTABLE QString deviceIdByName(const QString& name) const; Q_SCRIPTABLE virtual void sendSimpleNotification(const QString &eventId, const QString &title, const QString &text, const QString &iconName) = 0; Q_SIGNALS: Q_SCRIPTABLE void deviceAdded(const QString& id); Q_SCRIPTABLE void deviceRemoved(const QString& id); //Note that paired devices will never be removed Q_SCRIPTABLE void deviceVisibilityChanged(const QString& id, bool isVisible); Q_SCRIPTABLE void deviceListChanged(); //Emitted when any of deviceAdded, deviceRemoved or deviceVisibilityChanged is emitted Q_SCRIPTABLE void announcedNameChanged(const QString& announcedName); Q_SCRIPTABLE void pairingRequestsChanged(); private Q_SLOTS: void onNewDeviceLink(const NetworkPacket& identityPacket, DeviceLink* dl); void onDeviceStatusChanged(); private: void init(); protected: void addDevice(Device* device); bool isDiscoveringDevices() const; void removeDevice(Device* d); void cleanDevices(); QScopedPointer d; }; #endif diff --git a/tests/pluginloadtest.cpp b/tests/pluginloadtest.cpp index 3375980e..98f9cd89 100644 --- a/tests/pluginloadtest.cpp +++ b/tests/pluginloadtest.cpp @@ -1,85 +1,92 @@ /** * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/kdeconnectplugin.h" #include #include "kdeconnect-version.h" #include "testdaemon.h" class PluginLoadTest : public QObject { Q_OBJECT public: PluginLoadTest() { QStandardPaths::setTestModeEnabled(true); m_daemon = new TestDaemon; } private Q_SLOTS: void testPlugins() { + if (!(m_daemon->getLinkProviders().size() > 0)) { + QFAIL("No links available, but loopback should have been provided by the test"); + } + Device* d = nullptr; m_daemon->acquireDiscoveryMode(QStringLiteral("plugintest")); const QList devicesList = m_daemon->devicesList(); for (Device* id : devicesList) { if (id->isReachable()) { if (!id->isTrusted()) id->requestPair(); d = id; break; } } + if (d == nullptr) { + QFAIL("Unable to determine device"); + } m_daemon->releaseDiscoveryMode(QStringLiteral("plugintest")); if (!d->loadedPlugins().contains(QStringLiteral("kdeconnect_remotecontrol"))) { QSKIP("kdeconnect_remotecontrol is required for this test"); } QVERIFY(d); QVERIFY(d->isTrusted()); QVERIFY(d->isReachable()); d->setPluginEnabled(QStringLiteral("kdeconnect_mousepad"), false); QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), false); QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol")); d->setPluginEnabled(QStringLiteral("kdeconnect_mousepad"), true); QCOMPARE(d->isPluginEnabled("kdeconnect_mousepad"), true); QVERIFY(d->supportedPlugins().contains("kdeconnect_remotecontrol")); } private: TestDaemon* m_daemon; }; QTEST_MAIN(PluginLoadTest); #include "pluginloadtest.moc" diff --git a/tests/sendfiletest.cpp b/tests/sendfiletest.cpp index 28373391..892adc7c 100644 --- a/tests/sendfiletest.cpp +++ b/tests/sendfiletest.cpp @@ -1,154 +1,160 @@ /** * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include "core/daemon.h" #include "core/device.h" #include "core/kdeconnectplugin.h" #include #include "kdeconnect-version.h" #include "testdaemon.h" #include #include class TestSendFile : public QObject { Q_OBJECT public: TestSendFile() { QStandardPaths::setTestModeEnabled(true); m_daemon = new TestDaemon; } private Q_SLOTS: void testSend() { + if (!(m_daemon->getLinkProviders().size() > 0)) { + QFAIL("No links available, but loopback should have been provided by the test"); + } + m_daemon->acquireDiscoveryMode(QStringLiteral("test")); Device* d = nullptr; const QList devicesList = m_daemon->devicesList(); for (Device* id : devicesList) { if (id->isReachable()) { if (!id->isTrusted()) id->requestPair(); d = id; } } + if (d == nullptr) { + QFAIL("Unable to determine device"); + } m_daemon->releaseDiscoveryMode(QStringLiteral("test")); - QVERIFY(d); QCOMPARE(d->isReachable(), true); QCOMPARE(d->isTrusted(), true); QByteArray content("12312312312313213123213123"); QTemporaryFile temp; temp.open(); temp.write(content); temp.close(); KdeConnectPlugin* plugin = d->plugin(QStringLiteral("kdeconnect_share")); QVERIFY(plugin); plugin->metaObject()->invokeMethod(plugin, "shareUrl", Q_ARG(QString, QUrl::fromLocalFile(temp.fileName()).toString())); QSignalSpy spy(plugin, SIGNAL(shareReceived(QString))); QVERIFY(spy.wait(2000)); QVariantList args = spy.takeFirst(); QUrl sentFile(args.first().toUrl()); QFile file(sentFile.toLocalFile()); QCOMPARE(file.size(), content.size()); QVERIFY(file.open(QIODevice::ReadOnly)); QCOMPARE(file.readAll(), content); } void testSslJobs() { const QString aFile = QFINDTESTDATA("sendfiletest.cpp"); const QString destFile = QDir::tempPath() + "/kdeconnect-test-sentfile"; QFile(destFile).remove(); const QString deviceId = KdeConnectConfig::instance()->deviceId() , deviceName = QStringLiteral("testdevice") , deviceType = KdeConnectConfig::instance()->deviceType(); KdeConnectConfig* kcc = KdeConnectConfig::instance(); kcc->addTrustedDevice(deviceId, deviceName, deviceType); kcc->setDeviceProperty(deviceId, QStringLiteral("certificate"), QString::fromLatin1(kcc->certificate().toPem())); // Using same certificate from kcc, instead of generating //We need the device to be loaded on the daemon, otherwise CompositeUploadJob will get a null device Device* device = new Device(this, deviceId); m_daemon->addDevice(device); QSharedPointer f(new QFile(aFile)); NetworkPacket np(PACKET_TYPE_SHARE_REQUEST); np.setPayload(f, f->size()); CompositeUploadJob* job = new CompositeUploadJob(deviceId, false); UploadJob* uj = new UploadJob(np); job->addSubjob(uj); QSignalSpy spyUpload(job, &KJob::result); job->start(); f->open(QIODevice::ReadWrite); FileTransferJob* ft = np.createPayloadTransferJob(QUrl::fromLocalFile(destFile)); QSignalSpy spyTransfer(ft, &KJob::result); ft->start(); QVERIFY(spyTransfer.count() || spyTransfer.wait()); if (ft->error()) { qWarning() << "fterror" << ft->errorString(); } QCOMPARE(ft->error(), 0); QCOMPARE(spyUpload.count(), 1); QFile resultFile(destFile), originFile(aFile); QVERIFY(resultFile.open(QIODevice::ReadOnly)); QVERIFY(originFile.open(QIODevice::ReadOnly)); const QByteArray resultContents = resultFile.readAll(), originContents = originFile.readAll(); QCOMPARE(resultContents.size(), originContents.size()); QCOMPARE(resultFile.readAll(), originFile.readAll()); } private: TestDaemon* m_daemon; }; QTEST_MAIN(TestSendFile); #include "sendfiletest.moc" diff --git a/tests/testdaemon.h b/tests/testdaemon.h index 3144c601..dd64d105 100644 --- a/tests/testdaemon.h +++ b/tests/testdaemon.h @@ -1,70 +1,73 @@ /** * Copyright 2015 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef TESTDAEMON_H #define TESTDAEMON_H #include #include class TestDaemon : public Daemon { public: TestDaemon(QObject* parent = nullptr) : Daemon(parent, true) , m_nam(nullptr) { + // Necessary to force the event loop to run because the test harness seems to behave differently + // and we need the QTimer::SingleShot in Daemon's constructor to fire + QCoreApplication::processEvents(); } void addDevice(Device* device) { Daemon::addDevice(device); } void reportError(const QString & title, const QString & description) override { qWarning() << "error:" << title << description; } void askPairingConfirmation(Device * d) override { d->acceptPairing(); } QNetworkAccessManager* networkAccessManager() override { if (!m_nam) { m_nam = new KIO::AccessManager(this); } return m_nam; } Q_SCRIPTABLE virtual void sendSimpleNotification(const QString &eventId, const QString &title, const QString &text, const QString &iconName) override { qDebug() << eventId << title << text << iconName; } void quit() override { qDebug() << "quit was called"; } private: QNetworkAccessManager* m_nam; }; #endif