#include #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(&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(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::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(); } }