Implement pairing via GUI

This commit is contained in:
Cameron Gutman 2018-07-05 23:12:55 -07:00
parent 1b1ad86271
commit 6687936e2f
9 changed files with 155 additions and 33 deletions

View File

@ -4,6 +4,7 @@
#include <QThread> #include <QThread>
#include <QUdpSocket> #include <QUdpSocket>
#include <QHostInfo> #include <QHostInfo>
#include <QThreadPool>
#define SER_HOSTS "hosts" #define SER_HOSTS "hosts"
#define SER_NAME "hostname" #define SER_NAME "hostname"
@ -331,27 +332,54 @@ QVector<NvComputer*> ComputerManager::getComputers()
return QVector<NvComputer*>::fromList(m_KnownHosts.values()); return QVector<NvComputer*>::fromList(m_KnownHosts.values());
} }
void ComputerManager::deleteHost(NvComputer* computer) class DeferredHostDeletionTask : public QRunnable
{ {
QWriteLocker lock(&m_Lock); public:
DeferredHostDeletionTask(ComputerManager* cm, NvComputer* computer)
: m_Computer(computer),
m_ComputerManager(cm) {}
QThread* pollingThread = m_PollThreads[computer->uuid]; void run()
if (pollingThread != nullptr) { {
pollingThread->requestInterruption(); QWriteLocker lock(&m_ComputerManager->m_Lock);
// We must wait here because we're going to delete computer QThread* pollingThread = m_ComputerManager->m_PollThreads[m_Computer->uuid];
// and we can't do that out from underneath the poller. if (pollingThread != nullptr) {
pollingThread->wait(); pollingThread->requestInterruption();
// The thread is safe to delete now // We must wait here because we're going to delete computer
Q_ASSERT(pollingThread->isFinished()); // and we can't do that out from underneath the poller.
delete pollingThread; pollingThread->wait();
// The thread is safe to delete now
Q_ASSERT(pollingThread->isFinished());
delete pollingThread;
}
m_ComputerManager->m_PollThreads.remove(m_Computer->uuid);
m_ComputerManager->m_KnownHosts.remove(m_Computer->uuid);
delete m_Computer;
} }
m_PollThreads.remove(computer->uuid); private:
m_KnownHosts.remove(computer->uuid); NvComputer* m_Computer;
ComputerManager* m_ComputerManager;
};
delete computer; void ComputerManager::deleteHost(NvComputer* computer)
{
// Punt to a worker thread to avoid stalling the
// UI while waiting for the polling thread to die
QThreadPool::globalInstance()->start(new DeferredHostDeletionTask(this, computer));
}
void ComputerManager::pairHost(NvComputer* computer, QString pin)
{
// Punt to a worker thread to avoid stalling the
// UI while waiting for pairing to complete
PendingPairingTask* pairing = new PendingPairingTask(this, computer, pin);
QThreadPool::globalInstance()->start(pairing);
} }
void ComputerManager::stopPollingAsync() void ComputerManager::stopPollingAsync()

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "nvhttp.h" #include "nvhttp.h"
#include "nvpairingmanager.h"
#include <qmdnsengine/server.h> #include <qmdnsengine/server.h>
#include <qmdnsengine/cache.h> #include <qmdnsengine/cache.h>
@ -10,6 +11,7 @@
#include <QThread> #include <QThread>
#include <QReadWriteLock> #include <QReadWriteLock>
#include <QSettings> #include <QSettings>
#include <QRunnable>
class NvComputer class NvComputer
{ {
@ -239,6 +241,8 @@ class ComputerManager : public QObject
{ {
Q_OBJECT Q_OBJECT
friend class DeferredHostDeletionTask;
public: public:
explicit ComputerManager(QObject *parent = nullptr); explicit ComputerManager(QObject *parent = nullptr);
@ -248,6 +252,8 @@ public:
Q_INVOKABLE bool addNewHost(QString address, bool mdns); Q_INVOKABLE bool addNewHost(QString address, bool mdns);
void pairHost(NvComputer* computer, QString pin);
QVector<NvComputer*> getComputers(); QVector<NvComputer*> getComputers();
// computer is deleted inside this call // computer is deleted inside this call
@ -256,6 +262,8 @@ public:
signals: signals:
void computerStateChanged(NvComputer* computer); void computerStateChanged(NvComputer* computer);
void pairingCompleted(NvComputer* computer, QString error);
private slots: private slots:
void handleComputerStateChanged(NvComputer* computer); void handleComputerStateChanged(NvComputer* computer);
@ -275,3 +283,54 @@ private:
QMdnsEngine::Cache m_MdnsCache; QMdnsEngine::Cache m_MdnsCache;
QVector<MdnsPendingComputer*> m_PendingResolution; QVector<MdnsPendingComputer*> m_PendingResolution;
}; };
class PendingPairingTask : public QObject, public QRunnable
{
Q_OBJECT
public:
PendingPairingTask(ComputerManager* computerManager, NvComputer* computer, QString pin)
: m_ComputerManager(computerManager),
m_Computer(computer),
m_Pin(pin)
{
connect(this, &PendingPairingTask::pairingCompleted,
computerManager, &ComputerManager::pairingCompleted);
}
signals:
void pairingCompleted(NvComputer* computer, QString error);
private:
void run()
{
NvPairingManager pairingManager(m_Computer->activeAddress);
try {
NvPairingManager::PairState result = pairingManager.pair(m_Computer->appVersion, m_Pin);
switch (result)
{
case NvPairingManager::PairState::PIN_WRONG:
emit pairingCompleted(m_Computer, "The PIN from the PC didn't match. Please try again.");
break;
case NvPairingManager::PairState::FAILED:
emit pairingCompleted(m_Computer, "Pairing failed. Please try again.");
break;
case NvPairingManager::PairState::ALREADY_IN_PROGRESS:
emit pairingCompleted(m_Computer, "Another pairing attempt is already in progress.");
break;
case NvPairingManager::PairState::PAIRED:
emit pairingCompleted(m_Computer, nullptr);
break;
}
} catch (const GfeHttpResponseException& e) {
emit pairingCompleted(m_Computer,
QString::fromLocal8Bit(e.getStatusMessage()) +
" (Error " + QString::number(e.getStatusCode()));
}
}
ComputerManager* m_ComputerManager;
NvComputer* m_Computer;
QString m_Pin;
};

View File

@ -25,9 +25,8 @@ NvHTTP::NvHTTP(QString address) :
} }
QVector<int> QVector<int>
NvHTTP::getServerVersionQuad(QString serverInfo) NvHTTP::parseQuad(QString quad)
{ {
QString quad = getXmlString(serverInfo, "appversion");
QStringList parts = quad.split("."); QStringList parts = quad.split(".");
QVector<int> ret; QVector<int> ret;

View File

@ -91,7 +91,7 @@ public:
static static
QVector<int> QVector<int>
getServerVersionQuad(QString serverInfo); parseQuad(QString quad);
void void
quitApp(); quitApp();

View File

@ -42,12 +42,6 @@ NvPairingManager::~NvPairingManager()
EVP_PKEY_free(m_PrivateKey); EVP_PKEY_free(m_PrivateKey);
} }
QString
NvPairingManager::generatePinString()
{
return QString::asprintf("%04d", QRandomGenerator::global()->bounded(10000));
}
QByteArray QByteArray
NvPairingManager::generateRandomBytes(int length) NvPairingManager::generateRandomBytes(int length)
{ {
@ -167,9 +161,9 @@ NvPairingManager::saltPin(QByteArray salt, QString pin)
} }
NvPairingManager::PairState NvPairingManager::PairState
NvPairingManager::pair(QString serverInfo, QString pin) NvPairingManager::pair(QString appVersion, QString pin)
{ {
int serverMajorVersion = NvHTTP::getServerVersionQuad(serverInfo).at(0); int serverMajorVersion = NvHTTP::parseQuad(appVersion).at(0);
qDebug() << "Pairing with server generation: " << serverMajorVersion; qDebug() << "Pairing with server generation: " << serverMajorVersion;
QCryptographicHash::Algorithm hashAlgo; QCryptographicHash::Algorithm hashAlgo;

View File

@ -23,11 +23,8 @@ public:
~NvPairingManager(); ~NvPairingManager();
QString
generatePinString();
PairState PairState
pair(QString serverInfo, QString pin); pair(QString appVersion, QString pin);
private: private:
QByteArray QByteArray

View File

@ -8,6 +8,8 @@ import ComputerModel 1.0
import ComputerManager 1.0 import ComputerManager 1.0
GridView { GridView {
property ComputerModel computerModel : createModel()
anchors.fill: parent anchors.fill: parent
anchors.leftMargin: 5 anchors.leftMargin: 5
anchors.topMargin: 5 anchors.topMargin: 5
@ -26,10 +28,26 @@ GridView {
ComputerManager.stopPollingAsync() ComputerManager.stopPollingAsync()
} }
function pairingComplete(error)
{
// Close the PIN dialog
pairDialog.close()
// Start polling again
ComputerManager.startPolling()
// Display a failed dialog if we got an error
if (error !== null) {
pairingFailedDialog.text = error
pairingFailedDialog.open()
}
}
function createModel() function createModel()
{ {
var model = Qt.createQmlObject('import ComputerModel 1.0; ComputerModel {}', parent, '') var model = Qt.createQmlObject('import ComputerModel 1.0; ComputerModel {}', parent, '')
model.initialize(ComputerManager) model.initialize(ComputerManager)
model.pairingCompleted.connect(pairingComplete)
return model return model
} }
@ -114,12 +132,20 @@ GridView {
else { else {
if (!model.busy) { if (!model.busy) {
var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4) var pin = ("0000" + Math.floor(Math.random() * 10000)).slice(-4)
// Stop polling, since pairing may make GFE unresponsive
ComputerManager.stopPollingAsync()
// Kick off pairing in the background
computerModel.pairComputer(index, pin)
// Display the pairing dialog
pairDialog.pin = pin pairDialog.pin = pin
// TODO: initiate pairing request
pairDialog.open() pairDialog.open()
} }
else { else {
// cannot pair while something is streaming or attempting to pair // cannot pair while something is streaming or attempting to pair
pairingFailedDialog.text = "This PC is currently busy. Make sure to quit any running games and try again."
pairingFailedDialog.open() pairingFailedDialog.open()
} }
} }
@ -135,8 +161,7 @@ GridView {
id: pairingFailedDialog id: pairingFailedDialog
// don't allow edits to the rest of the window while open // don't allow edits to the rest of the window while open
modality:Qt.WindowModal modality:Qt.WindowModal
icon: StandardIcon.Critical
text:"This PC is busy: make sure no game is streaming, and try again"
standardButtons: StandardButton.Ok standardButtons: StandardButton.Ok
} }

View File

@ -1,5 +1,4 @@
#include "computermodel.h" #include "computermodel.h"
#include "backend/nvpairingmanager.h"
ComputerModel::ComputerModel(QObject* object) ComputerModel::ComputerModel(QObject* object)
: QAbstractListModel(object) {} : QAbstractListModel(object) {}
@ -9,6 +8,8 @@ void ComputerModel::initialize(ComputerManager* computerManager)
m_ComputerManager = computerManager; m_ComputerManager = computerManager;
connect(m_ComputerManager, &ComputerManager::computerStateChanged, connect(m_ComputerManager, &ComputerManager::computerStateChanged,
this, &ComputerModel::handleComputerStateChanged); this, &ComputerModel::handleComputerStateChanged);
connect(m_ComputerManager, &ComputerManager::pairingCompleted,
this, &ComputerModel::handlePairingCompleted);
m_Computers = m_ComputerManager->getComputers(); m_Computers = m_ComputerManager->getComputers();
} }
@ -92,6 +93,18 @@ void ComputerModel::deleteComputer(int computerIndex)
endRemoveRows(); endRemoveRows();
} }
void ComputerModel::pairComputer(int computerIndex, QString pin)
{
Q_ASSERT(computerIndex < m_Computers.count());
m_ComputerManager->pairHost(m_Computers[computerIndex], pin);
}
void ComputerModel::handlePairingCompleted(NvComputer*, QString error)
{
emit pairingCompleted(error.isNull() ? QVariant() : error);
}
void ComputerModel::handleComputerStateChanged(NvComputer* computer) void ComputerModel::handleComputerStateChanged(NvComputer* computer)
{ {
// If this is an existing computer, we can report the data changed // If this is an existing computer, we can report the data changed

View File

@ -29,9 +29,16 @@ public:
Q_INVOKABLE void deleteComputer(int computerIndex); Q_INVOKABLE void deleteComputer(int computerIndex);
Q_INVOKABLE void pairComputer(int computerIndex, QString pin);
signals:
void pairingCompleted(QVariant error);
private slots: private slots:
void handleComputerStateChanged(NvComputer* computer); void handleComputerStateChanged(NvComputer* computer);
void handlePairingCompleted(NvComputer* computer, QString error);
private: private:
QVector<NvComputer*> m_Computers; QVector<NvComputer*> m_Computers;
ComputerManager* m_ComputerManager; ComputerManager* m_ComputerManager;