diff options
Diffstat (limited to 'examples/network/torrent/trackerclient.cpp')
-rw-r--r-- | examples/network/torrent/trackerclient.cpp | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/examples/network/torrent/trackerclient.cpp b/examples/network/torrent/trackerclient.cpp new file mode 100644 index 0000000000..0b9a5815c9 --- /dev/null +++ b/examples/network/torrent/trackerclient.cpp @@ -0,0 +1,236 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor +** the names of its contributors may be used to endorse or promote +** products derived from this software without specific prior written +** permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "bencodeparser.h" +#include "connectionmanager.h" +#include "torrentclient.h" +#include "torrentserver.h" +#include "trackerclient.h" + +#include <QtCore> + +TrackerClient::TrackerClient(TorrentClient *downloader, QObject *parent) + : QObject(parent), torrentDownloader(downloader) +{ + length = 0; + requestInterval = 5 * 60; + requestIntervalTimer = -1; + firstTrackerRequest = true; + lastTrackerRequest = false; + firstSeeding = true; + + connect(&http, SIGNAL(done(bool)), this, SLOT(httpRequestDone(bool))); +} + +void TrackerClient::start(const MetaInfo &info) +{ + metaInfo = info; + QTimer::singleShot(0, this, SLOT(fetchPeerList())); + + if (metaInfo.fileForm() == MetaInfo::SingleFileForm) { + length = metaInfo.singleFile().length; + } else { + QList<MetaInfoMultiFile> files = metaInfo.multiFiles(); + for (int i = 0; i < files.size(); ++i) + length += files.at(i).length; + } +} + +void TrackerClient::startSeeding() +{ + firstSeeding = true; + fetchPeerList(); +} + +void TrackerClient::stop() +{ + lastTrackerRequest = true; + http.abort(); + fetchPeerList(); +} + +void TrackerClient::timerEvent(QTimerEvent *event) +{ + if (event->timerId() == requestIntervalTimer) { + if (http.state() == QHttp::Unconnected) + fetchPeerList(); + } else { + QObject::timerEvent(event); + } +} + +void TrackerClient::fetchPeerList() +{ + // Prepare connection details + QString fullUrl = metaInfo.announceUrl(); + QUrl url(fullUrl); + QString passkey = "?"; + if (fullUrl.contains("?passkey")) { + passkey = metaInfo.announceUrl().mid(fullUrl.indexOf("?passkey"), -1); + passkey += '&'; + } + + // Percent encode the hash + QByteArray infoHash = torrentDownloader->infoHash(); + QString encodedSum; + for (int i = 0; i < infoHash.size(); ++i) { + encodedSum += '%'; + encodedSum += QString::number(infoHash[i], 16).right(2).rightJustified(2, '0'); + } + + bool seeding = (torrentDownloader->state() == TorrentClient::Seeding); + QByteArray query; + query += url.path().toLatin1(); + query += passkey; + query += "info_hash=" + encodedSum; + query += "&peer_id=" + ConnectionManager::instance()->clientId(); + query += "&port=" + QByteArray::number(TorrentServer::instance()->serverPort()); + query += "&compact=1"; + query += "&uploaded=" + QByteArray::number(torrentDownloader->uploadedBytes()); + + if (!firstSeeding) { + query += "&downloaded=0"; + query += "&left=0"; + } else { + query += "&downloaded=" + QByteArray::number( + torrentDownloader->downloadedBytes()); + int left = qMax<int>(0, metaInfo.totalSize() - torrentDownloader->downloadedBytes()); + query += "&left=" + QByteArray::number(seeding ? 0 : left); + } + + if (seeding && firstSeeding) { + query += "&event=completed"; + firstSeeding = false; + } else if (firstTrackerRequest) { + firstTrackerRequest = false; + query += "&event=started"; + } else if(lastTrackerRequest) { + query += "&event=stopped"; + } + + if (!trackerId.isEmpty()) + query += "&trackerid=" + trackerId; + + http.setHost(url.host(), url.port() == -1 ? 80 : url.port()); + if (!url.userName().isEmpty()) + http.setUser(url.userName(), url.password()); + http.get(query); +} + +void TrackerClient::httpRequestDone(bool error) +{ + if (lastTrackerRequest) { + emit stopped(); + return; + } + + if (error) { + emit connectionError(http.error()); + return; + } + + QByteArray response = http.readAll(); + http.abort(); + + BencodeParser parser; + if (!parser.parse(response)) { + qWarning("Error parsing bencode response from tracker: %s", + qPrintable(parser.errorString())); + http.abort(); + return; + } + + QMap<QByteArray, QVariant> dict = parser.dictionary(); + + if (dict.contains("failure reason")) { + // no other items are present + emit failure(QString::fromUtf8(dict.value("failure reason").toByteArray())); + return; + } + + if (dict.contains("warning message")) { + // continue processing + emit warning(QString::fromUtf8(dict.value("warning message").toByteArray())); + } + + if (dict.contains("tracker id")) { + // store it + trackerId = dict.value("tracker id").toByteArray(); + } + + if (dict.contains("interval")) { + // Mandatory item + if (requestIntervalTimer != -1) + killTimer(requestIntervalTimer); + requestIntervalTimer = startTimer(dict.value("interval").toInt() * 1000); + } + + if (dict.contains("peers")) { + // store it + peers.clear(); + QVariant peerEntry = dict.value("peers"); + if (peerEntry.type() == QVariant::List) { + QList<QVariant> peerTmp = peerEntry.toList(); + for (int i = 0; i < peerTmp.size(); ++i) { + TorrentPeer tmp; + QMap<QByteArray, QVariant> peer = qvariant_cast<QMap<QByteArray, QVariant> >(peerTmp.at(i)); + tmp.id = QString::fromUtf8(peer.value("peer id").toByteArray()); + tmp.address.setAddress(QString::fromUtf8(peer.value("ip").toByteArray())); + tmp.port = peer.value("port").toInt(); + peers << tmp; + } + } else { + QByteArray peerTmp = peerEntry.toByteArray(); + for (int i = 0; i < peerTmp.size(); i += 6) { + TorrentPeer tmp; + uchar *data = (uchar *)peerTmp.constData() + i; + tmp.port = (int(data[4]) << 8) + data[5]; + uint ipAddress = 0; + ipAddress += uint(data[0]) << 24; + ipAddress += uint(data[1]) << 16; + ipAddress += uint(data[2]) << 8; + ipAddress += uint(data[3]); + tmp.address.setAddress(ipAddress); + peers << tmp; + } + } + emit peerListUpdated(peers); + } +} |