46 bool Secure,
const QList<QHostAddress> &Addresses)
53 uiAddress(QStringLiteral()),
56 apiVersion(QStringLiteral()),
58 m_sources(Spontaneous),
60 m_addresses(Addresses),
61 m_preferredAddressIndex(0),
63 m_getPeerDetailsRPC(nullptr),
64 m_getPeerDetails(nullptr),
65 m_webSocketThread(nullptr),
66 m_retryScheduled(false),
67 m_retryInterval(10000)
69 for (
int i = 0; i < m_addresses.size(); ++i)
71 if (m_addresses.at(i).protocol() == QAbstractSocket::IPv4Protocol)
72 m_preferredAddressIndex = i;
77 if (m_addresses.isEmpty())
78 m_debugString = QStringLiteral(
"No address found");
101 if (m_getPeerDetailsRPC && m_webSocketThread)
104 m_getPeerDetailsRPC->
DownRef();
105 m_getPeerDetailsRPC =
nullptr;
108 if (m_getPeerDetails)
112 m_getPeerDetails =
nullptr;
116 if (m_webSocketThread)
118 m_webSocketThread->quit();
119 m_webSocketThread->wait();
120 delete m_webSocketThread;
121 m_webSocketThread =
nullptr;
139 return UUID < gLocalContext->
GetUuid();
153 m_retryScheduled =
false;
156 if (m_addresses.isEmpty())
158 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"No valid peer addresses"));
171 if (m_webSocketThread)
174 if (gNetworkedContext)
185 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Not connecting to %1 - we have priority").arg(m_debugString));
189 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Trying to connect to %1").arg(m_debugString));
195 m_webSocketThread->start();
246 if (m_webSocketThread && m_webSocketThread == thread)
248 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Connection established with %1").arg(m_debugString));
253 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Unknown WebSocket connected..."));
260 if (m_webSocketThread && m_webSocketThread == thread)
262 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Connection with %1 closed").arg(m_debugString));
263 m_webSocketThread->quit();
264 m_webSocketThread->wait();
265 delete m_webSocketThread;
266 m_webSocketThread =
nullptr;
273 if (gNetworkedContext)
283 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Unknown WebSocket disconnected..."));
289 if (m_getPeerDetails && (Request == m_getPeerDetails))
291 QJsonDocument jsonresult = QJsonDocument::fromJson(Request->
GetBuffer());
293 m_getPeerDetails =
nullptr;
295 if (!jsonresult.isNull() && jsonresult.isObject())
297 QJsonObject
object = jsonresult.object();
299 if (
object.contains(QStringLiteral(
"details")))
301 QJsonObject details =
object.value(QStringLiteral(
"details")).toObject();
306 if (!jpriority.isNull() && !jstarttime.isNull() && !jversion.isNull())
309 priority = (int)jpriority.toDouble();
310 startTime = (qint64)jstarttime.toDouble();
321 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to retrieve peer information"));
326 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Failed to find 'details' in peer response"));
331 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Error parsing API return - expecting JSON object"));
334 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Response:\r\n%1").arg(jsonresult.toJson().constData()));
346 if (Request == m_getPeerDetailsRPC)
348 bool success =
false;
350 int state = m_getPeerDetailsRPC->
GetState();
354 message = QStringLiteral(
"Timed out");
358 message = QStringLiteral(
"Cancelled");
362 QVariantMap map = m_getPeerDetailsRPC->
GetReply().toMap();
363 QVariant error = map.value(QStringLiteral(
"error"));
364 if (error.type() == QVariant::Map)
366 QVariantMap errors = error.toMap();
367 message = QStringLiteral(
"'%1' (%2)").arg(errors.value(QStringLiteral(
"message")).toString())
368 .arg(errors.value(QStringLiteral(
"code")).toInt());
373 if (m_getPeerDetailsRPC->
GetReply().type() == QVariant::Map)
375 QVariantMap map = m_getPeerDetailsRPC->
GetReply().toMap();
380 if (!vpriority.isNull() && !vstarttime.isNull() && !vversion.isNull())
383 priority = (int)vpriority.toDouble();
384 startTime = (qint64)vstarttime.toDouble();
394 message = QStringLiteral(
"Incomplete details");
399 message = QStringLiteral(
"Unexpected variant type %1").arg(m_getPeerDetailsRPC->
GetReply().type());
405 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Call to '%1' failed (%2)").arg(m_getPeerDetailsRPC->
GetMethod(), message));
409 m_getPeerDetailsRPC->
DownRef();
410 m_getPeerDetailsRPC =
nullptr;
414 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Reply received"));
420 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Unknown request ready"));
424 void TorcNetworkService::ScheduleRetry(
void)
426 if (!m_retryScheduled)
429 m_retryScheduled =
true;
433 void TorcNetworkService::QueryPeerDetails(
void)
437 if (!m_webSocketThread)
439 if (m_getPeerDetails)
441 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Already running GetDetails HTTP request"));
445 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Querying peer details over HTTP"));
448 url.setScheme(secure ? QStringLiteral(
"https") : QStringLiteral(
"http"));
450 url.setHost(m_addresses.value(m_preferredAddressIndex).toString());
451 url.setPath(QStringLiteral(
"/services/GetDetails"));
452 QNetworkRequest networkrequest(url);
453 networkrequest.setRawHeader(QByteArrayLiteral(
"Accept"), QByteArrayLiteral(
"application/json"));
455 m_getPeerDetails =
new TorcNetworkRequest(networkrequest, QNetworkAccessManager::GetOperation, 0, &m_abort);
462 if (m_getPeerDetailsRPC)
464 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Already running GetDetails RPC"));
468 m_getPeerDetailsRPC =
new TorcRPCRequest(QStringLiteral(
"/services/GetDetails"),
this);
478 if (m_webSocketThread)
480 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Already have websocket - closing new socket"));
486 m_webSocketThread = Thread;
495 if (m_webSocketThread)
501 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Cannot fulfill remote request - not connected"));
511 if (m_webSocketThread)
514 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Cannot cancel request - not connected"));
523 result.insert(QStringLiteral(
"uiAddress"),
uiAddress);
539 m_debugString =
host +
":" + QString::number(
port);
569 m_sources &= !Source;
572 #define BLACKLIST QStringLiteral("submit,revert") 592 m_discoveredServices(),
615 if (!m_discoveredServices.isEmpty())
616 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Removing %1 discovered peers").arg(m_discoveredServices.size()));
617 while (!m_discoveredServices.isEmpty())
618 delete m_discoveredServices.takeLast();
632 result.append(service->
ToMap());
642 QString name = Peer->
GetName();
643 QString uuid = Peer->
GetUuid();
652 QString name = Peer->
GetName();
653 QString uuid = Peer->
GetUuid();
684 else if (m_serviceList.contains(uuid))
688 for (
int i = 0; i < m_discoveredServices.size(); ++i)
689 if (m_discoveredServices.at(i)->GetUuid() == uuid)
696 QList<QHostAddress> hosts;
697 foreach (
const QString &address, addresses)
701 QHostAddress hostaddress(address);
704 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Ignoring peer address '%1' - address is unreachable").arg(address));
708 hosts.append(QHostAddress(address));
716 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Ignoring peer '%1' - no useable network addresses.").arg(host));
721 QByteArray txtrecords =
event->Data().value(
TORC_BONJOUR_TXT).toByteArray();
769 else if (m_serviceList.contains(uuid))
773 for (
int i = 0; i < m_discoveredServices.size(); ++i)
774 if (m_discoveredServices.at(i)->GetUuid() == uuid)
781 QString name =
event->Data().value(
TORC_NAME).toString();
783 QList<QHostAddress> hosts;
784 hosts << QHostAddress(location.host());
789 service->SetHost(location.host());
792 emit service->TryConnect();
799 return QObject::event(Event);
812 if (!gNetworkedContext)
814 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Upgrade request but no TorcNetworkedContext singleton"));
819 emit gNetworkedContext->
NewPeer(Thread, Data);
825 if (!Request || UUID.isEmpty())
828 if (!gNetworkedContext)
830 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"RemoteRequest but no TorcNetworkedContext singleton"));
834 emit gNetworkedContext->
NewRequest(UUID, Request);
845 if (!Request || UUID.isEmpty())
848 if (!gNetworkedContext)
850 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"CancelRequest but no TorcNetworkedContext singleton"));
862 while (Request->
IsShared() && (count++ < Wait))
866 LOG(VB_GENERAL, LOG_ERR, QStringLiteral(
"Request is still shared after cancellation"));
873 if (!UUID.isEmpty() && m_serviceList.contains(UUID))
876 for (
int i = 0; i < m_discoveredServices.size(); ++i)
878 if (m_discoveredServices.value(i)->GetUuid() == UUID)
880 m_discoveredServices.value(i)->RemoteRequest(Request);
886 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Connection identified by '%1' unknown").arg(UUID));
891 if (!UUID.isEmpty() && m_serviceList.contains(UUID))
894 for (
int i = 0; i < m_discoveredServices.size(); ++i)
896 if (m_discoveredServices.value(i)->GetUuid() == UUID)
898 m_discoveredServices.value(i)->CancelRequest(Request);
904 LOG(VB_GENERAL, LOG_WARNING, QStringLiteral(
"Connection identified by '%1' unknown").arg(UUID));
917 QString UUID = Data.value(
TORC_UUID).toString();
918 QString name = Data.value(
TORC_NAME).toString();
919 int port = Data.value(
TORC_PORT).toInt();
923 QHostAddress address(Data.value(
TORC_ADDRESS).toString());
927 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Received WebSocket for peer without UUID (%1) - closing").arg(name));
932 if (m_serviceList.contains(UUID))
935 for (
int i = 0; i < m_discoveredServices.size(); ++i)
939 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Received unexpected WebSocket from peer '%1' (%2) - closing")
940 .arg(m_discoveredServices.value(i)->GetName(), UUID));
948 if (Thread == thread)
950 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Received WebSocket for new peer '%1' (%2)").arg(name, UUID));
951 QList<QHostAddress> addresses;
952 addresses << address;
967 if (Peer && !m_serviceList.contains(Peer->
GetUuid()))
971 m_discoveredServices.append(Peer);
974 m_serviceList.append(Peer->
GetUuid());
976 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"New Torc peer '%1' %2").arg(Peer->
GetName(), Peer->
GetUuid()));
982 if (m_serviceList.contains(UUID))
986 for (
int i = 0; i < m_discoveredServices.size(); ++i)
988 if (m_discoveredServices.at(i)->GetUuid() == UUID)
991 m_discoveredServices.at(i)->RemoveSource(Source);
998 m_discoveredServices.takeAt(i)->deleteLater();
1004 (void)m_serviceList.removeAll(UUID);
1006 LOG(VB_GENERAL, LOG_INFO, QStringLiteral(
"Torc peer %1 went away").arg(UUID));
1016 qRegisterMetaType<TorcRPCRequest*>();
1026 Strings.insert(QStringLiteral(
"NoPeers"), QCoreApplication::translate(
"TorcNetworkedContext",
"No other Torc devices detected"));
1028 (void)QCoreApplication::translate(
"TorcNetworkedContext",
"%n other Torc device(s) discovered",
nullptr, 2);
1041 gNetworkedContext =
nullptr;
void SetSource(ServiceSource Source)
void HandleNewPeer(TorcWebSocketThread *Thread, const QVariantMap &Data)
static bool WeActAsServer(int Priority, qint64 StartTime, const QString &UUID)
Determine whether we (the local device) should be the server for peer to peer communications.
void SetAPIVersion(const QString &Version)
void SubscriberDeleted(QObject *Subscriber)
void RemoteRequest(TorcRPCRequest *Request)
TorcNetworkService(const QString &Name, const QString &UUID, int Port, bool Secure, const QList< QHostAddress > &Addresses)
TorcLocalContext * gLocalContext
void RemoveSource(ServiceSource Source)
static QString UUIDFromUSN(const QString &USN)
void ConnectionUpgraded(void)
#define TORC_APIVERSION_B
void SetHost(const QString &Host)
QList< QHostAddress > GetAddresses(void)
A factory class for automatically running objects outside of the main loop.
A wrapper around QNetworkRequest.
static bool GetAsynchronous(TorcNetworkRequest *Request, QObject *Parent)
Queue an asynchronous HTTP request.
friend class TorcNetworkedContextObject
~TorcNetworkService()
Destroy this service.
QString GetUuid(void) const
static QString IPAddressToLiteral(const QHostAddress &Address, int Port, bool UseLocalhost=true)
Convert an IP address to a string literal.
TorcNetworkedContext * gNetworkedContext
virtual bool DownRef(void)
void NewRequest(const QString &UUID, TorcRPCRequest *Request)
void HandleNewRequest(const QString &UUID, TorcRPCRequest *Request)
QByteArray & GetBuffer(void)
void AddState(int State)
Progress the state for this request.
#define TORC_ADMIN_HIGH_PRIORITY
#define TORC_BONJOUR_ADDRESSES
void Connect(void)
Establish a WebSocket connection to the peer if necessary.
void HandleCancelRequest(const QString &UUID, TorcRPCRequest *Request)
A class encapsulating a Remote Procedure Call.
QVariantList GetPeers(void)
static void AddHostName(const QString &Host)
Register a known host name for this application.
#define TORC_BONJOUR_NAME
TorcNetworkedContextObject()
void CancelRequest(TorcRPCRequest *Request)
static void CancelRequest(const QString &UUID, TorcRPCRequest *Request, int Wait=1000)
Cancel Request associated with the connection identified by UUID.
void Connected(TorcNetworkService *Peer)
Encapsulates information on a discovered Torc peer.
static void PeerConnected(TorcWebSocketThread *Thread, const QVariantMap &Data)
Respond to a valid WebSocket upgrade request and schedule creation of a WebSocket on the give QTcpSoc...
QString GetMethod(void) const
void PriorityChanged(void)
void NewPeer(TorcWebSocketThread *Socket, const QVariantMap &Data)
bool event(QEvent *Event)
void SetPriority(int Priority)
void SetStartTime(qint64 StartTime)
~TorcNetworkedContextObject()
void PeerDisconnected(QString &Name, QString &UUID)
static Type TorcEventType
Register TorcEventType with QEvent.
static QMap< QByteArray, QByteArray > TxtRecordToMap(const QByteArray &TxtRecord)
Extracts a QMap from a properly formatted Bonjour Txt record.
void HandleSubscriberDeleted(QObject *Subscriber)
QString GetAPIVersion(void)
static void RemoteRequest(const QString &UUID, TorcRPCRequest *Request)
Pass Request to the remote connection identified by UUID.
QReadWriteLock m_httpServiceLock
#define TORC_SERVICE_VERSION
bool IsNotification(void) const
#define TORC_BONJOUR_HOST
static bool IsExternal(const QHostAddress &Address)
Returns true if the address is accessible from other devices.
void StartTimeChanged(void)
void ConnectedChanged(void)
qint64 GetStartTime(void)
static TorcBonjour * Instance(void)
Returns the global TorcBonjour singleton.
void RequestReady(TorcNetworkRequest *Request)
const QVariant & GetReply(void) const
void GetStrings(QVariantMap &Strings)
A general purpose event object.
#define LOG(_MASK_, _LEVEL_, _STRING_)
void SetWebSocketThread(TorcWebSocketThread *Thread)
void Disconnected(TorcNetworkService *Peer)
void CancelRequest(TorcRPCRequest *Request)
void AddObserver(QObject *Observer)
brief Register the given object to receive events.
A factory class to register translatable strings for use with external interfaces/applications.
#define TORC_BONJOUR_PORT
ServiceSources GetSources(void)
static void Cancel(TorcNetworkRequest *Request)
void ApiVersionChanged(void)
Wraps a TorcQThread around a TorcWebsocket.
qint64 GetStartTime(void)
void RemoveObserver(QObject *Observer)
brief Deregister the given object.
friend class TorcNetworkService
static TorcWebSocketThread * TakeSocket(TorcWebSocketThread *Socket)
static void RemoveHostName(const QString &Host)
Remove a host name from the known list of host names.
void RequestCancelled(const QString &UUID, TorcRPCRequest *Request)
void RemoteRequest(TorcRPCRequest *Request)
A class to discover and connect to other Torc applications.