summaryrefslogtreecommitdiff
path: root/distfoldd/discoverer.cc
diff options
context:
space:
mode:
Diffstat (limited to 'distfoldd/discoverer.cc')
-rw-r--r--distfoldd/discoverer.cc195
1 files changed, 195 insertions, 0 deletions
diff --git a/distfoldd/discoverer.cc b/distfoldd/discoverer.cc
new file mode 100644
index 0000000..629a955
--- /dev/null
+++ b/distfoldd/discoverer.cc
@@ -0,0 +1,195 @@
+#include <QtCore/QDebug>
+
+#include "discoverer.h"
+
+Discoverer::Discoverer(const QUuid &uuid, uint port, const QString &serviceName, QObject *parent) :
+ QObject(parent), _folderUuid(uuid), _hostUuid(QUuid::createUuid()),
+ _serviceName(serviceName), _port(port),
+ _mtime(), _mtimeChanged(false),
+ _receiver(new QUdpSocket(this)), _sender(new QUdpSocket(this)),
+ _netConfig(new QNetworkConfigurationManager(this)),
+ _netInfo(new QSystemNetworkInfo(this)),
+ _timer(new QSystemAlignedTimer(this)), _fallbackTimer(new QTimer(this))
+{
+ connect(_receiver, SIGNAL(readyRead()), SLOT(handleDataAvailable()));
+ if (!_receiver->bind(servicePort, QUdpSocket::ShareAddress)) {
+ qWarning() << "Failed to bind to discoverer port" << servicePort;
+ }
+ connect(_netConfig, SIGNAL(onlineStateChanged(bool)), SLOT(handleOnlineStateChanged(bool)));
+ connect(_timer, SIGNAL(timeout()), SLOT(handleTimerTimeout()));
+ connect(_fallbackTimer, SIGNAL(timeout()), SLOT(handleTimerTimeout()));
+ connect(_timer, SIGNAL(error(QSystemAlignedTimer::AlignedTimerError)), SLOT(handleTimerError()));
+ _timer->setSingleShot(true);
+ _fallbackTimer->setSingleShot(true);
+ if (_netConfig->isOnline() ||
+ _netConfig->allConfigurations(QNetworkConfiguration::Defined).empty()) {
+ // Either only, or _netConfig has no configs so it is useless.
+ qDebug() << "Start timer";
+ _timer->start(0, activeBroadcastInterval);
+ if (_timer && _timer->lastError() != QSystemAlignedTimer::NoError) {
+ qDebug() << "QSystemAlignedTimer broken at start";
+ delete _timer;
+ _timer = 0;
+ _fallbackTimer->start(activeBroadcastInterval * 1000);
+ }
+ } else {
+ qDebug() << "I am NOT online";
+ }
+}
+
+void Discoverer::setLastModifiedTime(const QDateTime& dateTime)
+{
+ _mtime = dateTime;
+ if (_knownHosts.empty()) {
+ // Idle mode: do nothing.
+ } else {
+ // Active mode
+ switchToActiveMode();
+ }
+ _mtimeChanged = true;
+}
+
+QByteArray Discoverer::encodeMessage() const
+{
+ QByteArray ba;
+ QByteArray hostUuid = _hostUuid.toString().toAscii();
+ QByteArray folderUuid = _folderUuid.toString().toAscii();
+ QByteArray serviceNameUtf8 = _serviceName.toUtf8();
+ Message msg;
+
+ Q_ASSERT(hostUuid.length() == sizeof(msg.hostUuid));
+ Q_ASSERT(folderUuid.length() == sizeof(msg.folderUuid));
+
+ ba.reserve(sizeof(msg) + serviceNameUtf8.size());
+
+ msg.mtime = _mtime.toTime_t();
+ strncpy(msg.hostUuid, hostUuid.data(), sizeof(msg.hostUuid));
+ msg.port = _port;
+ strncpy(msg.folderUuid, folderUuid.data(), sizeof(msg.folderUuid));
+
+ ba.append(reinterpret_cast<char*>(&msg), sizeof(msg));
+ ba.append(serviceNameUtf8);
+ return ba;
+}
+
+void Discoverer::switchToActiveMode()
+{
+ if (_timer) {
+ if (!_timer->isActive() || _timer->maximumInterval() > activeBroadcastInterval) {
+ if (_timer->isActive()) _timer->wokeUp();
+ _timer->start(0, activeBroadcastInterval);
+ }
+ } else if (!_fallbackTimer->isActive() || _fallbackTimer->interval() > activeBroadcastInterval * 1000) {
+ _fallbackTimer->start(activeBroadcastInterval * 1000);
+ }
+}
+
+void Discoverer::broadcastMessage()
+{
+ _sender->writeDatagram(encodeMessage(), QHostAddress::Broadcast, servicePort);
+ _lastBroadcast = QDateTime::currentDateTime();
+ _mtimeChanged = false;
+ qDebug() << "Broadcast message at" << _lastBroadcast;
+}
+
+void Discoverer::handleDataAvailable()
+{
+ while (_receiver->hasPendingDatagrams()) {
+ QByteArray data;
+ QHostAddress sender;
+ data.resize(_receiver->pendingDatagramSize());
+ _receiver->readDatagram(data.data(), data.size(), &sender);
+
+ handleBroadcastMessage(sender, data);
+ }
+}
+
+void Discoverer::handleBroadcastMessage(const QHostAddress& sender, const QByteArray &data)
+{
+ const char *dataPtr = data.data();
+ const Message *msg = reinterpret_cast<const Message*>(dataPtr);
+ QString serviceName = QString::fromUtf8(&dataPtr[sizeof(Message)]);
+ char uuidData[sizeof(msg->hostUuid)];
+
+ strncpy(uuidData, msg->hostUuid, sizeof(uuidData));
+ QUuid hostUuid(QString::fromAscii(uuidData, sizeof(uuidData)));
+ strncpy(uuidData, msg->folderUuid, sizeof(uuidData));
+ QUuid folderUuid(QString::fromAscii(uuidData, sizeof(uuidData)));
+
+ if (hostUuid.isNull() || folderUuid.isNull()) {
+ qWarning() << "Null uuid";
+ return;
+ }
+ if (hostUuid == _hostUuid) {
+ // A message from myself, ignore
+ return;
+ }
+ if (folderUuid != _folderUuid) {
+ // A message not about the same folder, ignore
+ return;
+ }
+
+ qDebug() << "Got message from" << sender << msg->port;
+ handleHostSeen(hostUuid);
+
+ QDateTime mtime = QDateTime::fromTime_t(msg->mtime);
+ qDebug() << mtime << _mtime << _mtime.secsTo(mtime);
+ if (_mtime.secsTo(mtime) > dateTimeCompareMinDelta) {
+ // We are outdated, let's try to connect to them.
+ emit foundMoreRecentHost(sender, msg->port, mtime);
+ }
+}
+
+void Discoverer::handleHostSeen(const QUuid &hostUuid)
+{
+ bool host_previously_seen = _knownHosts.contains(hostUuid);
+ _knownHosts[hostUuid].lastSeen = QDateTime::currentDateTime();
+ if (!host_previously_seen || _mtimeChanged) {
+ switchToActiveMode();
+ }
+}
+
+void Discoverer::handleTimerTimeout()
+{
+ qDebug() << "Timer";
+ broadcastMessage();
+ QDateTime now = QDateTime::currentDateTime();
+ QHash<QUuid, HostInfo>::iterator i = _knownHosts.begin();
+ while (i != _knownHosts.end()) {
+ if (i.value().lastSeen.secsTo(now) > lostHostTimeout) {
+ i = _knownHosts.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ qDebug() << "Known hosts" << _knownHosts.size();
+ if (_timer) {
+ _timer->start(idleBroadcastInterval * 0.7f, idleBroadcastInterval * 1.3f);
+ } else {
+ _fallbackTimer->start(idleBroadcastInterval * 1000);
+ }
+}
+
+void Discoverer::handleTimerError()
+{
+ qDebug() << "SystemAlignedTimer broken";
+ _timer->deleteLater();
+ _timer = 0;
+ _fallbackTimer->start(activeBroadcastInterval * 1000);
+}
+
+void Discoverer::handleOnlineStateChanged(bool online)
+{
+ if (online) {
+ qDebug() << "Online";
+ if (_netInfo->currentMode() == QSystemNetworkInfo::UnknownMode ||
+ _netInfo->currentMode() == QSystemNetworkInfo::WlanMode) {
+ switchToActiveMode();
+ }
+ } else {
+ qDebug() << "Offline";
+ _knownHosts.clear();
+ _fallbackTimer->stop();
+ if (_timer) _timer->stop();
+ }
+}