summaryrefslogtreecommitdiffstats
path: root/examples/network/torrent
diff options
context:
space:
mode:
authorQt by Nokia <qt-info@nokia.com>2011-04-27 12:05:43 +0200
committeraxis <qt-info@nokia.com>2011-04-27 12:05:43 +0200
commit38be0d13830efd2d98281c645c3a60afe05ffece (patch)
tree6ea73f3ec77f7d153333779883e8120f82820abe /examples/network/torrent
Initial import from the monolithic Qt.
This is the beginning of revision history for this module. If you want to look at revision history older than this, please refer to the Qt Git wiki for how to use Git history grafting. At the time of writing, this wiki is located here: http://qt.gitorious.org/qt/pages/GitIntroductionWithQt If you have already performed the grafting and you don't see any history beyond this commit, try running "git log" with the "--follow" argument. Branched from the monolithic repo, Qt master branch, at commit 896db169ea224deb96c59ce8af800d019de63f12
Diffstat (limited to 'examples/network/torrent')
-rw-r--r--examples/network/torrent/addtorrentdialog.cpp169
-rw-r--r--examples/network/torrent/addtorrentdialog.h73
-rw-r--r--examples/network/torrent/bencodeparser.cpp234
-rw-r--r--examples/network/torrent/bencodeparser.h80
-rw-r--r--examples/network/torrent/connectionmanager.cpp88
-rw-r--r--examples/network/torrent/connectionmanager.h65
-rw-r--r--examples/network/torrent/filemanager.cpp446
-rw-r--r--examples/network/torrent/filemanager.h143
-rw-r--r--examples/network/torrent/forms/addtorrentform.ui266
-rw-r--r--examples/network/torrent/icons.qrc12
-rw-r--r--examples/network/torrent/icons/1downarrow.pngbin0 -> 895 bytes
-rw-r--r--examples/network/torrent/icons/1uparrow.pngbin0 -> 822 bytes
-rw-r--r--examples/network/torrent/icons/bottom.pngbin0 -> 1632 bytes
-rw-r--r--examples/network/torrent/icons/edit_add.pngbin0 -> 394 bytes
-rw-r--r--examples/network/torrent/icons/edit_remove.pngbin0 -> 368 bytes
-rw-r--r--examples/network/torrent/icons/exit.pngbin0 -> 1426 bytes
-rw-r--r--examples/network/torrent/icons/peertopeer.pngbin0 -> 10072 bytes
-rw-r--r--examples/network/torrent/icons/player_pause.pngbin0 -> 690 bytes
-rw-r--r--examples/network/torrent/icons/player_play.pngbin0 -> 900 bytes
-rw-r--r--examples/network/torrent/icons/player_stop.pngbin0 -> 627 bytes
-rw-r--r--examples/network/torrent/icons/stop.pngbin0 -> 1252 bytes
-rw-r--r--examples/network/torrent/main.cpp57
-rw-r--r--examples/network/torrent/mainwindow.cpp712
-rw-r--r--examples/network/torrent/mainwindow.h131
-rw-r--r--examples/network/torrent/metainfo.cpp217
-rw-r--r--examples/network/torrent/metainfo.h121
-rw-r--r--examples/network/torrent/peerwireclient.cpp664
-rw-r--r--examples/network/torrent/peerwireclient.h209
-rw-r--r--examples/network/torrent/ratecontroller.cpp155
-rw-r--r--examples/network/torrent/ratecontroller.h79
-rw-r--r--examples/network/torrent/torrent.pro39
-rw-r--r--examples/network/torrent/torrentclient.cpp1528
-rw-r--r--examples/network/torrent/torrentclient.h204
-rw-r--r--examples/network/torrent/torrentserver.cpp103
-rw-r--r--examples/network/torrent/torrentserver.h71
-rw-r--r--examples/network/torrent/trackerclient.cpp236
-rw-r--r--examples/network/torrent/trackerclient.h103
37 files changed, 6205 insertions, 0 deletions
diff --git a/examples/network/torrent/addtorrentdialog.cpp b/examples/network/torrent/addtorrentdialog.cpp
new file mode 100644
index 0000000000..c78535a309
--- /dev/null
+++ b/examples/network/torrent/addtorrentdialog.cpp
@@ -0,0 +1,169 @@
+/****************************************************************************
+**
+** 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 "addtorrentdialog.h"
+#include "metainfo.h"
+
+#include <QFile>
+#include <QFileDialog>
+#include <QLineEdit>
+#include <QMetaObject>
+
+static QString stringNumber(qint64 number)
+{
+ QString tmp;
+ if (number > (1024 * 1024 * 1024))
+ tmp.sprintf("%.2fGB", number / (1024.0 * 1024.0 * 1024.0));
+ else if (number > (1024 * 1024))
+ tmp.sprintf("%.2fMB", number / (1024.0 * 1024.0));
+ else if (number > (1024))
+ tmp.sprintf("%.2fKB", number / (1024.0));
+ else
+ tmp.sprintf("%d bytes", int(number));
+ return tmp;
+}
+
+AddTorrentDialog::AddTorrentDialog(QWidget *parent)
+ : QDialog(parent, Qt::Sheet)
+{
+ ui.setupUi(this);
+
+ connect(ui.browseTorrents, SIGNAL(clicked()),
+ this, SLOT(selectTorrent()));
+ connect(ui.browseDestination, SIGNAL(clicked()),
+ this, SLOT(selectDestination()));
+ connect(ui.torrentFile, SIGNAL(textChanged(QString)),
+ this, SLOT(setTorrent(QString)));
+
+ ui.destinationFolder->setText(destinationDirectory = QDir::current().path());
+ ui.torrentFile->setFocus();
+}
+
+void AddTorrentDialog::selectTorrent()
+{
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Choose a torrent file"),
+ lastDirectory,
+ tr("Torrents (*.torrent);; All files (*.*)"));
+ if (fileName.isEmpty())
+ return;
+ lastDirectory = QFileInfo(fileName).absolutePath();
+ setTorrent(fileName);
+}
+
+void AddTorrentDialog::selectDestination()
+{
+ QString dir = QFileDialog::getExistingDirectory(this, tr("Choose a destination directory"),
+ lastDestinationDirectory);
+ if (dir.isEmpty())
+ return;
+ lastDestinationDirectory = destinationDirectory = dir;
+ ui.destinationFolder->setText(destinationDirectory);
+ enableOkButton();
+}
+
+void AddTorrentDialog::enableOkButton()
+{
+ ui.okButton->setEnabled(!ui.destinationFolder->text().isEmpty()
+ && !ui.torrentFile->text().isEmpty());
+}
+
+void AddTorrentDialog::setTorrent(const QString &torrentFile)
+{
+ if (torrentFile.isEmpty()) {
+ enableOkButton();
+ return;
+ }
+
+ ui.torrentFile->setText(torrentFile);
+ if (!torrentFile.isEmpty())
+ lastDirectory = QFileInfo(torrentFile).absolutePath();
+
+ if (lastDestinationDirectory.isEmpty())
+ lastDestinationDirectory = lastDirectory;
+
+ MetaInfo metaInfo;
+ QFile torrent(torrentFile);
+ if (!torrent.open(QFile::ReadOnly) || !metaInfo.parse(torrent.readAll())) {
+ enableOkButton();
+ return;
+ }
+
+ ui.torrentFile->setText(torrentFile);
+ ui.announceUrl->setText(metaInfo.announceUrl());
+ if (metaInfo.comment().isEmpty())
+ ui.commentLabel->setText("<unknown>");
+ else
+ ui.commentLabel->setText(metaInfo.comment());
+ if (metaInfo.createdBy().isEmpty())
+ ui.creatorLabel->setText("<unknown>");
+ else
+ ui.creatorLabel->setText(metaInfo.createdBy());
+ ui.sizeLabel->setText(stringNumber(metaInfo.totalSize()));
+ if (metaInfo.fileForm() == MetaInfo::SingleFileForm) {
+ ui.torrentContents->setHtml(metaInfo.singleFile().name);
+ } else {
+ QString html;
+ foreach (MetaInfoMultiFile file, metaInfo.multiFiles()) {
+ QString name = metaInfo.name();
+ if (!name.isEmpty()) {
+ html += name;
+ if (!name.endsWith('/'))
+ html += '/';
+ }
+ html += file.path + "<br>";
+ }
+ ui.torrentContents->setHtml(html);
+ }
+
+ QFileInfo info(torrentFile);
+ ui.destinationFolder->setText(info.absolutePath());
+
+ enableOkButton();
+}
+
+QString AddTorrentDialog::torrentFileName() const
+{
+ return ui.torrentFile->text();
+}
+
+QString AddTorrentDialog::destinationFolder() const
+{
+ return ui.destinationFolder->text();
+}
diff --git a/examples/network/torrent/addtorrentdialog.h b/examples/network/torrent/addtorrentdialog.h
new file mode 100644
index 0000000000..09aa48c354
--- /dev/null
+++ b/examples/network/torrent/addtorrentdialog.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef ADDTORRENTDIALOG_H
+#define ADDTORRENTDIALOG_H
+
+#include <QDialog>
+
+#include "ui_addtorrentform.h"
+
+class AddTorrentDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ AddTorrentDialog(QWidget *parent = 0);
+
+ QString torrentFileName() const;
+ QString destinationFolder() const;
+
+public slots:
+ void setTorrent(const QString &torrentFile);
+
+private slots:
+ void selectTorrent();
+ void selectDestination();
+ void enableOkButton();
+
+private:
+ Ui_AddTorrentFile ui;
+ QString destinationDirectory;
+ QString lastDirectory;
+ QString lastDestinationDirectory;
+};
+
+#endif
diff --git a/examples/network/torrent/bencodeparser.cpp b/examples/network/torrent/bencodeparser.cpp
new file mode 100644
index 0000000000..edb04c6d6b
--- /dev/null
+++ b/examples/network/torrent/bencodeparser.cpp
@@ -0,0 +1,234 @@
+/****************************************************************************
+**
+** 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 <QList>
+#include <QMetaType>
+
+BencodeParser::BencodeParser()
+{
+}
+
+bool BencodeParser::parse(const QByteArray &content)
+{
+ if (content.isEmpty()) {
+ errString = QString("No content");
+ return false;
+ }
+
+ this->content = content;
+ index = 0;
+ infoStart = 0;
+ infoLength = 0;
+ return getDictionary(&dictionaryValue);
+}
+
+QString BencodeParser::errorString() const
+{
+ return errString;
+}
+
+QMap<QByteArray, QVariant> BencodeParser::dictionary() const
+{
+ return dictionaryValue;
+}
+
+QByteArray BencodeParser::infoSection() const
+{
+ return content.mid(infoStart, infoLength);
+}
+
+bool BencodeParser::getByteString(QByteArray *byteString)
+{
+ const int contentSize = content.size();
+ int size = -1;
+ do {
+ char c = content.at(index);
+ if (c < '0' || c > '9') {
+ if (size == -1)
+ return false;
+ if (c != ':') {
+ errString = QString("Unexpected character at pos %1: %2")
+ .arg(index).arg(c);
+ return false;
+ }
+ ++index;
+ break;
+ }
+ if (size == -1)
+ size = 0;
+ size *= 10;
+ size += c - '0';
+ } while (++index < contentSize);
+
+ if (byteString)
+ *byteString = content.mid(index, size);
+ index += size;
+ return true;
+}
+
+bool BencodeParser::getInteger(qint64 *integer)
+{
+ const int contentSize = content.size();
+ if (content.at(index) != 'i')
+ return false;
+
+ ++index;
+ qint64 num = -1;
+ bool negative = false;
+
+ do {
+ char c = content.at(index);
+ if (c < '0' || c > '9') {
+ if (num == -1) {
+ if (c != '-' || negative)
+ return false;
+ negative = true;
+ continue;
+ } else {
+ if (c != 'e') {
+ errString = QString("Unexpected character at pos %1: %2")
+ .arg(index).arg(c);
+ return false;
+ }
+ ++index;
+ break;
+ }
+ }
+ if (num == -1)
+ num = 0;
+ num *= 10;
+ num += c - '0';
+ } while (++index < contentSize);
+
+ if (integer)
+ *integer = negative ? -num : num;
+ return true;
+}
+
+bool BencodeParser::getList(QList<QVariant> *list)
+{
+ const int contentSize = content.size();
+ if (content.at(index) != 'l')
+ return false;
+
+ QList<QVariant> tmp;
+ ++index;
+
+ do {
+ if (content.at(index) == 'e') {
+ ++index;
+ break;
+ }
+
+ qint64 number;
+ QByteArray byteString;
+ QList<QVariant> tmpList;
+ QMap<QByteArray, QVariant> dictionary;
+
+ if (getInteger(&number))
+ tmp << number;
+ else if (getByteString(&byteString))
+ tmp << byteString;
+ else if (getList(&tmpList))
+ tmp << tmpList;
+ else if (getDictionary(&dictionary))
+ tmp << QVariant::fromValue<QMap<QByteArray, QVariant> >(dictionary);
+ else {
+ errString = QString("error at index %1").arg(index);
+ return false;
+ }
+ } while (index < contentSize);
+
+ if (list)
+ *list = tmp;
+ return true;
+}
+
+bool BencodeParser::getDictionary(QMap<QByteArray, QVariant> *dictionary)
+{
+ const int contentSize = content.size();
+ if (content.at(index) != 'd')
+ return false;
+
+ QMap<QByteArray, QVariant> tmp;
+ ++index;
+
+ do {
+ if (content.at(index) == 'e') {
+ ++index;
+ break;
+ }
+
+ QByteArray key;
+ if (!getByteString(&key))
+ break;
+
+ if (key == "info")
+ infoStart = index;
+
+ qint64 number;
+ QByteArray byteString;
+ QList<QVariant> tmpList;
+ QMap<QByteArray, QVariant> dictionary;
+
+ if (getInteger(&number))
+ tmp.insert(key, number);
+ else if (getByteString(&byteString))
+ tmp.insert(key, byteString);
+ else if (getList(&tmpList))
+ tmp.insert(key, tmpList);
+ else if (getDictionary(&dictionary))
+ tmp.insert(key, QVariant::fromValue<QMap<QByteArray, QVariant> >(dictionary));
+ else {
+ errString = QString("error at index %1").arg(index);
+ return false;
+ }
+
+ if (key == "info")
+ infoLength = index - infoStart;
+
+ } while (index < contentSize);
+
+ if (dictionary)
+ *dictionary = tmp;
+ return true;
+}
diff --git a/examples/network/torrent/bencodeparser.h b/examples/network/torrent/bencodeparser.h
new file mode 100644
index 0000000000..de8b8ffd93
--- /dev/null
+++ b/examples/network/torrent/bencodeparser.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef BENCODEPARSER_H
+#define BENCODEPARSER_H
+
+#include <QByteArray>
+#include <QMap>
+#include <QString>
+#include <QVariant>
+#include <QList>
+
+typedef QMap<QByteArray,QVariant> Dictionary;
+Q_DECLARE_METATYPE(Dictionary)
+
+class BencodeParser
+{
+public:
+ BencodeParser();
+
+ bool parse(const QByteArray &content);
+ QString errorString() const;
+
+ QMap<QByteArray, QVariant> dictionary() const;
+ QByteArray infoSection() const;
+
+private:
+ bool getByteString(QByteArray *byteString);
+ bool getInteger(qint64 *integer);
+ bool getList(QList<QVariant> *list);
+ bool getDictionary(QMap<QByteArray, QVariant> *dictionary);
+
+ QMap<QByteArray, QVariant> dictionaryValue;
+
+ QString errString;
+ QByteArray content;
+ int index;
+
+ int infoStart;
+ int infoLength;
+};
+
+#endif
diff --git a/examples/network/torrent/connectionmanager.cpp b/examples/network/torrent/connectionmanager.cpp
new file mode 100644
index 0000000000..889933ea83
--- /dev/null
+++ b/examples/network/torrent/connectionmanager.cpp
@@ -0,0 +1,88 @@
+/****************************************************************************
+**
+** 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 "connectionmanager.h"
+
+#include <QByteArray>
+#include <QDateTime>
+
+static const int MaxConnections = 250;
+
+Q_GLOBAL_STATIC(ConnectionManager, connectionManager)
+
+ConnectionManager *ConnectionManager::instance()
+{
+ return connectionManager();
+}
+
+bool ConnectionManager::canAddConnection() const
+{
+ return (connections.size() < MaxConnections);
+}
+
+void ConnectionManager::addConnection(PeerWireClient *client)
+{
+ connections << client;
+}
+
+void ConnectionManager::removeConnection(PeerWireClient *client)
+{
+ connections.remove(client);
+}
+
+int ConnectionManager::maxConnections() const
+{
+ return MaxConnections;
+}
+
+QByteArray ConnectionManager::clientId() const
+{
+ if (id.isEmpty()) {
+ // Generate peer id
+ int startupTime = int(QDateTime::currentDateTime().toTime_t());
+
+ QString s;
+ s.sprintf("-QT%04x-", (QT_VERSION % 0xffff00) >> 8);
+ id += s.toLatin1();
+ id += QByteArray::number(startupTime, 10);
+ id += QByteArray(20 - id.size(), '-');
+ }
+ return id;
+}
diff --git a/examples/network/torrent/connectionmanager.h b/examples/network/torrent/connectionmanager.h
new file mode 100644
index 0000000000..ef38507e45
--- /dev/null
+++ b/examples/network/torrent/connectionmanager.h
@@ -0,0 +1,65 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef CONNECTIONMANAGER_H
+#define CONNECTIONMANAGER_H
+
+class PeerWireClient;
+
+#include <QByteArray>
+#include <QSet>
+
+class ConnectionManager
+{
+public:
+ static ConnectionManager *instance();
+
+ bool canAddConnection() const;
+ void addConnection(PeerWireClient *connection);
+ void removeConnection(PeerWireClient *connection);
+ int maxConnections() const;
+ QByteArray clientId() const;
+
+ private:
+ QSet<PeerWireClient *> connections;
+ mutable QByteArray id;
+};
+
+#endif
diff --git a/examples/network/torrent/filemanager.cpp b/examples/network/torrent/filemanager.cpp
new file mode 100644
index 0000000000..b4dfb25bca
--- /dev/null
+++ b/examples/network/torrent/filemanager.cpp
@@ -0,0 +1,446 @@
+/****************************************************************************
+**
+** 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 "filemanager.h"
+#include "metainfo.h"
+
+#include <QByteArray>
+#include <QDir>
+#include <QFile>
+#include <QTimer>
+#include <QTimerEvent>
+#include <QCryptographicHash>
+
+FileManager::FileManager(QObject *parent)
+ : QThread(parent)
+{
+ quit = false;
+ totalLength = 0;
+ readId = 0;
+ startVerification = false;
+ wokeUp = false;
+ newFile = false;
+ numPieces = 0;
+ verifiedPieces.fill(false);
+}
+
+FileManager::~FileManager()
+{
+ quit = true;
+ cond.wakeOne();
+ wait();
+
+ foreach (QFile *file, files) {
+ file->close();
+ delete file;
+ }
+}
+
+int FileManager::read(int pieceIndex, int offset, int length)
+{
+ ReadRequest request;
+ request.pieceIndex = pieceIndex;
+ request.offset = offset;
+ request.length = length;
+
+ QMutexLocker locker(&mutex);
+ request.id = readId++;
+ readRequests << request;
+
+ if (!wokeUp) {
+ wokeUp = true;
+ QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
+ }
+
+ return request.id;
+}
+
+void FileManager::write(int pieceIndex, int offset, const QByteArray &data)
+{
+ WriteRequest request;
+ request.pieceIndex = pieceIndex;
+ request.offset = offset;
+ request.data = data;
+
+ QMutexLocker locker(&mutex);
+ writeRequests << request;
+
+ if (!wokeUp) {
+ wokeUp = true;
+ QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
+ }
+}
+
+void FileManager::verifyPiece(int pieceIndex)
+{
+ QMutexLocker locker(&mutex);
+ pendingVerificationRequests << pieceIndex;
+ startVerification = true;
+
+ if (!wokeUp) {
+ wokeUp = true;
+ QMetaObject::invokeMethod(this, "wakeUp", Qt::QueuedConnection);
+ }
+}
+
+int FileManager::pieceLengthAt(int pieceIndex) const
+{
+ QMutexLocker locker(&mutex);
+ return (sha1s.size() == pieceIndex + 1)
+ ? (totalLength % pieceLength) : pieceLength;
+}
+
+QBitArray FileManager::completedPieces() const
+{
+ QMutexLocker locker(&mutex);
+ return verifiedPieces;
+}
+
+void FileManager::setCompletedPieces(const QBitArray &pieces)
+{
+ QMutexLocker locker(&mutex);
+ verifiedPieces = pieces;
+}
+
+QString FileManager::errorString() const
+{
+ return errString;
+}
+
+void FileManager::run()
+{
+ if (!generateFiles())
+ return;
+
+ do {
+ {
+ // Go to sleep if there's nothing to do.
+ QMutexLocker locker(&mutex);
+ if (!quit && readRequests.isEmpty() && writeRequests.isEmpty() && !startVerification)
+ cond.wait(&mutex);
+ }
+
+ // Read pending read requests
+ mutex.lock();
+ QList<ReadRequest> newReadRequests = readRequests;
+ readRequests.clear();
+ mutex.unlock();
+ while (!newReadRequests.isEmpty()) {
+ ReadRequest request = newReadRequests.takeFirst();
+ QByteArray block = readBlock(request.pieceIndex, request.offset, request.length);
+ emit dataRead(request.id, request.pieceIndex, request.offset, block);
+ }
+
+ // Write pending write requests
+ mutex.lock();
+ QList<WriteRequest> newWriteRequests = writeRequests;
+ writeRequests.clear();
+ while (!quit && !newWriteRequests.isEmpty()) {
+ WriteRequest request = newWriteRequests.takeFirst();
+ writeBlock(request.pieceIndex, request.offset, request.data);
+ }
+
+ // Process pending verification requests
+ if (startVerification) {
+ newPendingVerificationRequests = pendingVerificationRequests;
+ pendingVerificationRequests.clear();
+ verifyFileContents();
+ startVerification = false;
+ }
+ mutex.unlock();
+ newPendingVerificationRequests.clear();
+
+ } while (!quit);
+
+ // Write pending write requests
+ mutex.lock();
+ QList<WriteRequest> newWriteRequests = writeRequests;
+ writeRequests.clear();
+ mutex.unlock();
+ while (!newWriteRequests.isEmpty()) {
+ WriteRequest request = newWriteRequests.takeFirst();
+ writeBlock(request.pieceIndex, request.offset, request.data);
+ }
+}
+
+void FileManager::startDataVerification()
+{
+ QMutexLocker locker(&mutex);
+ startVerification = true;
+ cond.wakeOne();
+}
+
+bool FileManager::generateFiles()
+{
+ numPieces = -1;
+
+ // Set up the thread local data
+ if (metaInfo.fileForm() == MetaInfo::SingleFileForm) {
+ QMutexLocker locker(&mutex);
+ MetaInfoSingleFile singleFile = metaInfo.singleFile();
+
+ QString prefix;
+ if (!destinationPath.isEmpty()) {
+ prefix = destinationPath;
+ if (!prefix.endsWith("/"))
+ prefix += "/";
+ QDir dir;
+ if (!dir.mkpath(prefix)) {
+ errString = tr("Failed to create directory %1").arg(prefix);
+ emit error();
+ return false;
+ }
+ }
+ QFile *file = new QFile(prefix + singleFile.name);
+ if (!file->open(QFile::ReadWrite)) {
+ errString = tr("Failed to open/create file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ return false;
+ }
+
+ if (file->size() != singleFile.length) {
+ newFile = true;
+ if (!file->resize(singleFile.length)) {
+ errString = tr("Failed to resize file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ return false;
+ }
+ }
+ fileSizes << file->size();
+ files << file;
+ file->close();
+
+ pieceLength = singleFile.pieceLength;
+ totalLength = singleFile.length;
+ sha1s = singleFile.sha1Sums;
+ } else {
+ QMutexLocker locker(&mutex);
+ QDir dir;
+ QString prefix;
+
+ if (!destinationPath.isEmpty()) {
+ prefix = destinationPath;
+ if (!prefix.endsWith("/"))
+ prefix += "/";
+ }
+ if (!metaInfo.name().isEmpty()) {
+ prefix += metaInfo.name();
+ if (!prefix.endsWith("/"))
+ prefix += "/";
+ }
+ if (!dir.mkpath(prefix)) {
+ errString = tr("Failed to create directory %1").arg(prefix);
+ emit error();
+ return false;
+ }
+
+ foreach (const MetaInfoMultiFile &entry, metaInfo.multiFiles()) {
+ QString filePath = QFileInfo(prefix + entry.path).path();
+ if (!QFile::exists(filePath)) {
+ if (!dir.mkpath(filePath)) {
+ errString = tr("Failed to create directory %1").arg(filePath);
+ emit error();
+ return false;
+ }
+ }
+
+ QFile *file = new QFile(prefix + entry.path);
+ if (!file->open(QFile::ReadWrite)) {
+ errString = tr("Failed to open/create file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ return false;
+ }
+
+ if (file->size() != entry.length) {
+ newFile = true;
+ if (!file->resize(entry.length)) {
+ errString = tr("Failed to resize file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ return false;
+ }
+ }
+ fileSizes << file->size();
+ files << file;
+ file->close();
+
+ totalLength += entry.length;
+ }
+
+ sha1s = metaInfo.sha1Sums();
+ pieceLength = metaInfo.pieceLength();
+ }
+ numPieces = sha1s.size();
+ return true;
+}
+
+QByteArray FileManager::readBlock(int pieceIndex, int offset, int length)
+{
+ QByteArray block;
+ qint64 startReadIndex = (quint64(pieceIndex) * pieceLength) + offset;
+ qint64 currentIndex = 0;
+
+ for (int i = 0; !quit && i < files.size() && length > 0; ++i) {
+ QFile *file = files[i];
+ qint64 currentFileSize = fileSizes.at(i);
+ if ((currentIndex + currentFileSize) > startReadIndex) {
+ if (!file->isOpen()) {
+ if (!file->open(QFile::ReadWrite)) {
+ errString = tr("Failed to read from file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ break;
+ }
+ }
+
+ file->seek(startReadIndex - currentIndex);
+ QByteArray chunk = file->read(qMin<qint64>(length, currentFileSize - file->pos()));
+ file->close();
+
+ block += chunk;
+ length -= chunk.size();
+ startReadIndex += chunk.size();
+ if (length < 0) {
+ errString = tr("Failed to read from file %1 (read %3 bytes): %2")
+ .arg(file->fileName()).arg(file->errorString()).arg(length);
+ emit error();
+ break;
+ }
+ }
+ currentIndex += currentFileSize;
+ }
+ return block;
+}
+
+bool FileManager::writeBlock(int pieceIndex, int offset, const QByteArray &data)
+{
+ qint64 startWriteIndex = (qint64(pieceIndex) * pieceLength) + offset;
+ qint64 currentIndex = 0;
+ int bytesToWrite = data.size();
+ int written = 0;
+
+ for (int i = 0; !quit && i < files.size(); ++i) {
+ QFile *file = files[i];
+ qint64 currentFileSize = fileSizes.at(i);
+
+ if ((currentIndex + currentFileSize) > startWriteIndex) {
+ if (!file->isOpen()) {
+ if (!file->open(QFile::ReadWrite)) {
+ errString = tr("Failed to write to file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ break;
+ }
+ }
+
+ file->seek(startWriteIndex - currentIndex);
+ qint64 bytesWritten = file->write(data.constData() + written,
+ qMin<qint64>(bytesToWrite, currentFileSize - file->pos()));
+ file->close();
+
+ if (bytesWritten <= 0) {
+ errString = tr("Failed to write to file %1: %2")
+ .arg(file->fileName()).arg(file->errorString());
+ emit error();
+ return false;
+ }
+
+ written += bytesWritten;
+ startWriteIndex += bytesWritten;
+ bytesToWrite -= bytesWritten;
+ if (bytesToWrite == 0)
+ break;
+ }
+ currentIndex += currentFileSize;
+ }
+ return true;
+}
+
+void FileManager::verifyFileContents()
+{
+ // Verify all pieces the first time
+ if (newPendingVerificationRequests.isEmpty()) {
+ if (verifiedPieces.count(true) == 0) {
+ verifiedPieces.resize(sha1s.size());
+
+ int oldPercent = 0;
+ if (!newFile) {
+ int numPieces = sha1s.size();
+
+ for (int index = 0; index < numPieces; ++index) {
+ verifySinglePiece(index);
+
+ int percent = ((index + 1) * 100) / numPieces;
+ if (oldPercent != percent) {
+ emit verificationProgress(percent);
+ oldPercent = percent;
+ }
+ }
+ }
+ }
+ emit verificationDone();
+ return;
+ }
+
+ // Verify all pending pieces
+ foreach (int index, newPendingVerificationRequests)
+ emit pieceVerified(index, verifySinglePiece(index));
+}
+
+bool FileManager::verifySinglePiece(int pieceIndex)
+{
+ QByteArray block = readBlock(pieceIndex, 0, pieceLength);
+ QByteArray sha1Sum = QCryptographicHash::hash(block, QCryptographicHash::Sha1);
+
+ if (sha1Sum != sha1s.at(pieceIndex))
+ return false;
+ verifiedPieces.setBit(pieceIndex);
+ return true;
+}
+
+void FileManager::wakeUp()
+{
+ QMutexLocker locker(&mutex);
+ wokeUp = false;
+ cond.wakeOne();
+}
diff --git a/examples/network/torrent/filemanager.h b/examples/network/torrent/filemanager.h
new file mode 100644
index 0000000000..cf1a0f706b
--- /dev/null
+++ b/examples/network/torrent/filemanager.h
@@ -0,0 +1,143 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef FILEMANAGER_H
+#define FILEMANAGER_H
+
+#include <QBitArray>
+#include <QList>
+#include <QMutex>
+#include <QThread>
+#include <QWaitCondition>
+
+#include "metainfo.h"
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+class QFile;
+class QTimerEvent;
+QT_END_NAMESPACE
+
+class FileManager : public QThread
+{
+ Q_OBJECT
+
+public:
+ FileManager(QObject *parent = 0);
+ virtual ~FileManager();
+
+ inline void setMetaInfo(const MetaInfo &info) { metaInfo = info; }
+ inline void setDestinationFolder(const QString &directory) { destinationPath = directory; }
+
+ int read(int pieceIndex, int offset, int length);
+ void write(int pieceIndex, int offset, const QByteArray &data);
+ void verifyPiece(int pieceIndex);
+ inline qint64 totalSize() const { return totalLength; }
+
+ inline int pieceCount() const { return numPieces; }
+ int pieceLengthAt(int pieceIndex) const;
+
+ QBitArray completedPieces() const;
+ void setCompletedPieces(const QBitArray &pieces);
+
+ QString errorString() const;
+
+public slots:
+ void startDataVerification();
+
+signals:
+ void dataRead(int id, int pieceIndex, int offset, const QByteArray &data);
+ void error();
+ void verificationProgress(int percent);
+ void verificationDone();
+ void pieceVerified(int pieceIndex, bool verified);
+
+protected:
+ void run();
+
+private slots:
+ bool verifySinglePiece(int pieceIndex);
+ void wakeUp();
+
+private:
+ bool generateFiles();
+ QByteArray readBlock(int pieceIndex, int offset, int length);
+ bool writeBlock(int pieceIndex, int offset, const QByteArray &data);
+ void verifyFileContents();
+
+ struct WriteRequest {
+ int pieceIndex;
+ int offset;
+ QByteArray data;
+ };
+ struct ReadRequest {
+ int pieceIndex;
+ int offset;
+ int length;
+ int id;
+ };
+
+ QString errString;
+ QString destinationPath;
+ MetaInfo metaInfo;
+ QList<QFile *> files;
+ QList<QByteArray> sha1s;
+ QBitArray verifiedPieces;
+
+ bool newFile;
+ int pieceLength;
+ qint64 totalLength;
+ int numPieces;
+ int readId;
+ bool startVerification;
+ bool quit;
+ bool wokeUp;
+
+ QList<WriteRequest> writeRequests;
+ QList<ReadRequest> readRequests;
+ QList<int> pendingVerificationRequests;
+ QList<int> newPendingVerificationRequests;
+ QList<qint64> fileSizes;
+
+ mutable QMutex mutex;
+ mutable QWaitCondition cond;
+};
+
+#endif
diff --git a/examples/network/torrent/forms/addtorrentform.ui b/examples/network/torrent/forms/addtorrentform.ui
new file mode 100644
index 0000000000..950bb67119
--- /dev/null
+++ b/examples/network/torrent/forms/addtorrentform.ui
@@ -0,0 +1,266 @@
+<ui version="4.0" >
+ <author></author>
+ <comment></comment>
+ <exportmacro></exportmacro>
+ <class>AddTorrentFile</class>
+ <widget class="QDialog" name="AddTorrentFile" >
+ <property name="geometry" >
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>464</width>
+ <height>385</height>
+ </rect>
+ </property>
+ <property name="windowTitle" >
+ <string>Add a torrent</string>
+ </property>
+ <property name="sizeGripEnabled" >
+ <bool>false</bool>
+ </property>
+ <property name="modal" >
+ <bool>true</bool>
+ </property>
+ <layout class="QVBoxLayout" >
+ <property name="margin" >
+ <number>8</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox" >
+ <property name="title" >
+ <string>Select a torrent source</string>
+ </property>
+ <layout class="QGridLayout" >
+ <property name="margin" >
+ <number>8</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item row="6" column="0" >
+ <widget class="QLabel" name="label_4" >
+ <property name="text" >
+ <string>Destination:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1" colspan="2" >
+ <widget class="QLineEdit" name="torrentFile" />
+ </item>
+ <item row="1" column="0" >
+ <widget class="QLabel" name="label_2" >
+ <property name="text" >
+ <string>Tracker URL:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3" >
+ <widget class="QPushButton" name="browseTorrents" >
+ <property name="text" >
+ <string>Browse</string>
+ </property>
+ <property name="default" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0" >
+ <widget class="QLabel" name="label_5" >
+ <property name="text" >
+ <string>File(s):</string>
+ </property>
+ <property name="alignment" >
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0" >
+ <widget class="QLabel" name="label_3" >
+ <property name="text" >
+ <string>Size:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0" >
+ <widget class="QLabel" name="label_6" >
+ <property name="text" >
+ <string>Creator:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1" colspan="3" >
+ <widget class="QTextEdit" name="torrentContents" >
+ <property name="focusPolicy" >
+ <enum>Qt::NoFocus</enum>
+ </property>
+ <property name="tabChangesFocus" >
+ <bool>true</bool>
+ </property>
+ <property name="lineWrapMode" >
+ <enum>QTextEdit::NoWrap</enum>
+ </property>
+ <property name="readOnly" >
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1" colspan="2" >
+ <widget class="QLineEdit" name="destinationFolder" >
+ <property name="focusPolicy" >
+ <enum>Qt::StrongFocus</enum>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1" colspan="3" >
+ <widget class="QLabel" name="announceUrl" >
+ <property name="text" >
+ <string>&lt;none></string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0" >
+ <widget class="QLabel" name="label" >
+ <property name="text" >
+ <string>Torrent file:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="3" >
+ <widget class="QPushButton" name="browseDestination" >
+ <property name="text" >
+ <string>Browse</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0" >
+ <widget class="QLabel" name="label_7" >
+ <property name="text" >
+ <string>Comment:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1" colspan="3" >
+ <widget class="QLabel" name="commentLabel" >
+ <property name="text" >
+ <string>&lt;none></string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1" colspan="3" >
+ <widget class="QLabel" name="creatorLabel" >
+ <property name="text" >
+ <string>&lt;none></string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1" colspan="3" >
+ <widget class="QLabel" name="sizeLabel" >
+ <property name="text" >
+ <string>0</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ <widget class="QWidget" name="widget" >
+ <property name="geometry" >
+ <rect>
+ <x>10</x>
+ <y>40</y>
+ <width>364</width>
+ <height>33</height>
+ </rect>
+ </property>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" >
+ <property name="margin" >
+ <number>0</number>
+ </property>
+ <property name="spacing" >
+ <number>6</number>
+ </property>
+ <item>
+ <spacer>
+ <property name="orientation" >
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" >
+ <size>
+ <width>131</width>
+ <height>31</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="okButton" >
+ <property name="enabled" >
+ <bool>false</bool>
+ </property>
+ <property name="text" >
+ <string>&amp;OK</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="cancelButton" >
+ <property name="text" >
+ <string>&amp;Cancel</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <pixmapfunction></pixmapfunction>
+ <tabstops>
+ <tabstop>torrentFile</tabstop>
+ <tabstop>browseTorrents</tabstop>
+ <tabstop>torrentContents</tabstop>
+ <tabstop>destinationFolder</tabstop>
+ <tabstop>browseDestination</tabstop>
+ <tabstop>okButton</tabstop>
+ <tabstop>cancelButton</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>okButton</sender>
+ <signal>clicked()</signal>
+ <receiver>AddTorrentFile</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>278</x>
+ <y>253</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>96</x>
+ <y>254</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>cancelButton</sender>
+ <signal>clicked()</signal>
+ <receiver>AddTorrentFile</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel" >
+ <x>369</x>
+ <y>253</y>
+ </hint>
+ <hint type="destinationlabel" >
+ <x>179</x>
+ <y>282</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
diff --git a/examples/network/torrent/icons.qrc b/examples/network/torrent/icons.qrc
new file mode 100644
index 0000000000..9541ef7600
--- /dev/null
+++ b/examples/network/torrent/icons.qrc
@@ -0,0 +1,12 @@
+<!DOCTYPE RCC><RCC version="1.0">
+<qresource prefix="/">
+ <file>icons/peertopeer.png</file>
+ <file>icons/1uparrow.png</file>
+ <file>icons/1downarrow.png</file>
+ <file>icons/bottom.png</file>
+ <file>icons/player_pause.png</file>
+ <file>icons/player_play.png</file>
+ <file>icons/player_stop.png</file>
+ <file>icons/exit.png</file>
+</qresource>
+</RCC>
diff --git a/examples/network/torrent/icons/1downarrow.png b/examples/network/torrent/icons/1downarrow.png
new file mode 100644
index 0000000000..08403b82ba
--- /dev/null
+++ b/examples/network/torrent/icons/1downarrow.png
Binary files differ
diff --git a/examples/network/torrent/icons/1uparrow.png b/examples/network/torrent/icons/1uparrow.png
new file mode 100644
index 0000000000..f044811787
--- /dev/null
+++ b/examples/network/torrent/icons/1uparrow.png
Binary files differ
diff --git a/examples/network/torrent/icons/bottom.png b/examples/network/torrent/icons/bottom.png
new file mode 100644
index 0000000000..fe66b5d028
--- /dev/null
+++ b/examples/network/torrent/icons/bottom.png
Binary files differ
diff --git a/examples/network/torrent/icons/edit_add.png b/examples/network/torrent/icons/edit_add.png
new file mode 100644
index 0000000000..85b022e7a4
--- /dev/null
+++ b/examples/network/torrent/icons/edit_add.png
Binary files differ
diff --git a/examples/network/torrent/icons/edit_remove.png b/examples/network/torrent/icons/edit_remove.png
new file mode 100644
index 0000000000..93361f5225
--- /dev/null
+++ b/examples/network/torrent/icons/edit_remove.png
Binary files differ
diff --git a/examples/network/torrent/icons/exit.png b/examples/network/torrent/icons/exit.png
new file mode 100644
index 0000000000..2f7ff43a71
--- /dev/null
+++ b/examples/network/torrent/icons/exit.png
Binary files differ
diff --git a/examples/network/torrent/icons/peertopeer.png b/examples/network/torrent/icons/peertopeer.png
new file mode 100644
index 0000000000..f4856dcec5
--- /dev/null
+++ b/examples/network/torrent/icons/peertopeer.png
Binary files differ
diff --git a/examples/network/torrent/icons/player_pause.png b/examples/network/torrent/icons/player_pause.png
new file mode 100644
index 0000000000..8c9bcc4556
--- /dev/null
+++ b/examples/network/torrent/icons/player_pause.png
Binary files differ
diff --git a/examples/network/torrent/icons/player_play.png b/examples/network/torrent/icons/player_play.png
new file mode 100644
index 0000000000..70daa339be
--- /dev/null
+++ b/examples/network/torrent/icons/player_play.png
Binary files differ
diff --git a/examples/network/torrent/icons/player_stop.png b/examples/network/torrent/icons/player_stop.png
new file mode 100644
index 0000000000..ce6585ae84
--- /dev/null
+++ b/examples/network/torrent/icons/player_stop.png
Binary files differ
diff --git a/examples/network/torrent/icons/stop.png b/examples/network/torrent/icons/stop.png
new file mode 100644
index 0000000000..52e593ab21
--- /dev/null
+++ b/examples/network/torrent/icons/stop.png
Binary files differ
diff --git a/examples/network/torrent/main.cpp b/examples/network/torrent/main.cpp
new file mode 100644
index 0000000000..1023ae85a7
--- /dev/null
+++ b/examples/network/torrent/main.cpp
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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 <QApplication>
+#include <QtCore>
+
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ qWarning("The usage of QHttp is not recommended anymore, please use QNetworkAccessManager.");
+ qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
+
+ Q_INIT_RESOURCE(icons);
+
+ MainWindow window;
+ window.show();
+
+ return app.exec();
+}
diff --git a/examples/network/torrent/mainwindow.cpp b/examples/network/torrent/mainwindow.cpp
new file mode 100644
index 0000000000..06dfca1ba2
--- /dev/null
+++ b/examples/network/torrent/mainwindow.cpp
@@ -0,0 +1,712 @@
+/****************************************************************************
+**
+** 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 <QtGui>
+
+#include "addtorrentdialog.h"
+#include "mainwindow.h"
+#include "ratecontroller.h"
+#include "torrentclient.h"
+
+// TorrentView extends QTreeWidget to allow drag and drop.
+class TorrentView : public QTreeWidget
+{
+ Q_OBJECT
+public:
+ TorrentView(QWidget *parent = 0);
+
+signals:
+ void fileDropped(const QString &fileName);
+
+protected:
+ void dragMoveEvent(QDragMoveEvent *event);
+ void dropEvent(QDropEvent *event);
+};
+
+// TorrentViewDelegate is used to draw the progress bars.
+class TorrentViewDelegate : public QItemDelegate
+{
+ Q_OBJECT
+public:
+ inline TorrentViewDelegate(MainWindow *mainWindow) : QItemDelegate(mainWindow) {}
+
+ inline void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index ) const
+ {
+ if (index.column() != 2) {
+ QItemDelegate::paint(painter, option, index);
+ return;
+ }
+
+ // Set up a QStyleOptionProgressBar to precisely mimic the
+ // environment of a progress bar.
+ QStyleOptionProgressBar progressBarOption;
+ progressBarOption.state = QStyle::State_Enabled;
+ progressBarOption.direction = QApplication::layoutDirection();
+ progressBarOption.rect = option.rect;
+ progressBarOption.fontMetrics = QApplication::fontMetrics();
+ progressBarOption.minimum = 0;
+ progressBarOption.maximum = 100;
+ progressBarOption.textAlignment = Qt::AlignCenter;
+ progressBarOption.textVisible = true;
+
+ // Set the progress and text values of the style option.
+ int progress = qobject_cast<MainWindow *>(parent())->clientForRow(index.row())->progress();
+ progressBarOption.progress = progress < 0 ? 0 : progress;
+ progressBarOption.text = QString().sprintf("%d%%", progressBarOption.progress);
+
+ // Draw the progress bar onto the view.
+ QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
+ }
+};
+
+MainWindow::MainWindow(QWidget *parent)
+ : QMainWindow(parent), quitDialog(0), saveChanges(false)
+{
+ // Initialize some static strings
+ QStringList headers;
+ headers << tr("Torrent") << tr("Peers/Seeds") << tr("Progress")
+ << tr("Down rate") << tr("Up rate") << tr("Status");
+
+ // Main torrent list
+ torrentView = new TorrentView(this);
+ torrentView->setItemDelegate(new TorrentViewDelegate(this));
+ torrentView->setHeaderLabels(headers);
+ torrentView->setSelectionBehavior(QAbstractItemView::SelectRows);
+ torrentView->setAlternatingRowColors(true);
+ torrentView->setRootIsDecorated(false);
+ setCentralWidget(torrentView);
+
+ // Set header resize modes and initial section sizes
+ QFontMetrics fm = fontMetrics();
+ QHeaderView *header = torrentView->header();
+ header->resizeSection(0, fm.width("typical-name-for-a-torrent.torrent"));
+ header->resizeSection(1, fm.width(headers.at(1) + " "));
+ header->resizeSection(2, fm.width(headers.at(2) + " "));
+ header->resizeSection(3, qMax(fm.width(headers.at(3) + " "), fm.width(" 1234.0 KB/s ")));
+ header->resizeSection(4, qMax(fm.width(headers.at(4) + " "), fm.width(" 1234.0 KB/s ")));
+ header->resizeSection(5, qMax(fm.width(headers.at(5) + " "), fm.width(tr("Downloading") + " ")));
+
+ // Create common actions
+ QAction *newTorrentAction = new QAction(QIcon(":/icons/bottom.png"), tr("Add &new torrent"), this);
+ pauseTorrentAction = new QAction(QIcon(":/icons/player_pause.png"), tr("&Pause torrent"), this);
+ removeTorrentAction = new QAction(QIcon(":/icons/player_stop.png"), tr("&Remove torrent"), this);
+
+ // File menu
+ QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
+ fileMenu->addAction(newTorrentAction);
+ fileMenu->addAction(pauseTorrentAction);
+ fileMenu->addAction(removeTorrentAction);
+ fileMenu->addSeparator();
+ fileMenu->addAction(QIcon(":/icons/exit.png"), tr("E&xit"), this, SLOT(close()));
+
+ // Help menu
+ QMenu *helpMenu = menuBar()->addMenu(tr("&Help"));
+ helpMenu->addAction(tr("&About"), this, SLOT(about()));
+ helpMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
+
+ // Top toolbar
+ QToolBar *topBar = new QToolBar(tr("Tools"));
+ addToolBar(Qt::TopToolBarArea, topBar);
+ topBar->setMovable(false);
+ topBar->addAction(newTorrentAction);
+ topBar->addAction(removeTorrentAction);
+ topBar->addAction(pauseTorrentAction);
+ topBar->addSeparator();
+ downActionTool = topBar->addAction(QIcon(tr(":/icons/1downarrow.png")), tr("Move down"));
+ upActionTool = topBar->addAction(QIcon(tr(":/icons/1uparrow.png")), tr("Move up"));
+
+ // Bottom toolbar
+ QToolBar *bottomBar = new QToolBar(tr("Rate control"));
+ addToolBar(Qt::BottomToolBarArea, bottomBar);
+ bottomBar->setMovable(false);
+ downloadLimitSlider = new QSlider(Qt::Horizontal);
+ downloadLimitSlider->setRange(0, 1000);
+ bottomBar->addWidget(new QLabel(tr("Max download:")));
+ bottomBar->addWidget(downloadLimitSlider);
+ bottomBar->addWidget((downloadLimitLabel = new QLabel(tr("0 KB/s"))));
+ downloadLimitLabel->setFixedSize(QSize(fm.width(tr("99999 KB/s")), fm.lineSpacing()));
+ bottomBar->addSeparator();
+ uploadLimitSlider = new QSlider(Qt::Horizontal);
+ uploadLimitSlider->setRange(0, 1000);
+ bottomBar->addWidget(new QLabel(tr("Max upload:")));
+ bottomBar->addWidget(uploadLimitSlider);
+ bottomBar->addWidget((uploadLimitLabel = new QLabel(tr("0 KB/s"))));
+ uploadLimitLabel->setFixedSize(QSize(fm.width(tr("99999 KB/s")), fm.lineSpacing()));
+
+ // Set up connections
+ connect(torrentView, SIGNAL(itemSelectionChanged()),
+ this, SLOT(setActionsEnabled()));
+ connect(torrentView, SIGNAL(fileDropped(QString)),
+ this, SLOT(acceptFileDrop(QString)));
+ connect(uploadLimitSlider, SIGNAL(valueChanged(int)),
+ this, SLOT(setUploadLimit(int)));
+ connect(downloadLimitSlider, SIGNAL(valueChanged(int)),
+ this, SLOT(setDownloadLimit(int)));
+ connect(newTorrentAction, SIGNAL(triggered()),
+ this, SLOT(addTorrent()));
+ connect(pauseTorrentAction, SIGNAL(triggered()),
+ this, SLOT(pauseTorrent()));
+ connect(removeTorrentAction, SIGNAL(triggered()),
+ this, SLOT(removeTorrent()));
+ connect(upActionTool, SIGNAL(triggered(bool)),
+ this, SLOT(moveTorrentUp()));
+ connect(downActionTool, SIGNAL(triggered(bool)),
+ this, SLOT(moveTorrentDown()));
+
+ // Load settings and start
+ setWindowTitle(tr("Torrent Client"));
+ setActionsEnabled();
+ QMetaObject::invokeMethod(this, "loadSettings", Qt::QueuedConnection);
+}
+
+QSize MainWindow::sizeHint() const
+{
+ const QHeaderView *header = torrentView->header();
+
+ // Add up the sizes of all header sections. The last section is
+ // stretched, so its size is relative to the size of the width;
+ // instead of counting it, we count the size of its largest value.
+ int width = fontMetrics().width(tr("Downloading") + " ");
+ for (int i = 0; i < header->count() - 1; ++i)
+ width += header->sectionSize(i);
+
+ return QSize(width, QMainWindow::sizeHint().height())
+ .expandedTo(QApplication::globalStrut());
+}
+
+const TorrentClient *MainWindow::clientForRow(int row) const
+{
+ // Return the client at the given row.
+ return jobs.at(row).client;
+}
+
+int MainWindow::rowOfClient(TorrentClient *client) const
+{
+ // Return the row that displays this client's status, or -1 if the
+ // client is not known.
+ int row = 0;
+ foreach (Job job, jobs) {
+ if (job.client == client)
+ return row;
+ ++row;
+ }
+ return -1;
+}
+
+void MainWindow::loadSettings()
+{
+ // Load base settings (last working directory, upload/download limits).
+ QSettings settings("Trolltech", "Torrent");
+ lastDirectory = settings.value("LastDirectory").toString();
+ if (lastDirectory.isEmpty())
+ lastDirectory = QDir::currentPath();
+ int up = settings.value("UploadLimit").toInt();
+ int down = settings.value("DownloadLimit").toInt();
+ uploadLimitSlider->setValue(up ? up : 170);
+ downloadLimitSlider->setValue(down ? down : 550);
+
+ // Resume all previous downloads.
+ int size = settings.beginReadArray("Torrents");
+ for (int i = 0; i < size; ++i) {
+ settings.setArrayIndex(i);
+ QByteArray resumeState = settings.value("resumeState").toByteArray();
+ QString fileName = settings.value("sourceFileName").toString();
+ QString dest = settings.value("destinationFolder").toString();
+
+ if (addTorrent(fileName, dest, resumeState)) {
+ TorrentClient *client = jobs.last().client;
+ client->setDownloadedBytes(settings.value("downloadedBytes").toLongLong());
+ client->setUploadedBytes(settings.value("uploadedBytes").toLongLong());
+ }
+ }
+}
+
+bool MainWindow::addTorrent()
+{
+ // Show the file dialog, let the user select what torrent to start downloading.
+ QString fileName = QFileDialog::getOpenFileName(this, tr("Choose a torrent file"),
+ lastDirectory,
+ tr("Torrents (*.torrent);;"
+ " All files (*.*)"));
+ if (fileName.isEmpty())
+ return false;
+ lastDirectory = QFileInfo(fileName).absolutePath();
+
+ // Show the "Add Torrent" dialog.
+ AddTorrentDialog *addTorrentDialog = new AddTorrentDialog(this);
+ addTorrentDialog->setTorrent(fileName);
+ addTorrentDialog->deleteLater();
+ if (!addTorrentDialog->exec())
+ return false;
+
+ // Add the torrent to our list of downloads
+ addTorrent(fileName, addTorrentDialog->destinationFolder());
+ if (!saveChanges) {
+ saveChanges = true;
+ QTimer::singleShot(1000, this, SLOT(saveSettings()));
+ }
+ return true;
+}
+
+void MainWindow::removeTorrent()
+{
+ // Find the row of the current item, and find the torrent client
+ // for that row.
+ int row = torrentView->indexOfTopLevelItem(torrentView->currentItem());
+ TorrentClient *client = jobs.at(row).client;
+
+ // Stop the client.
+ client->disconnect();
+ connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
+ client->stop();
+
+ // Remove the row from the view.
+ delete torrentView->takeTopLevelItem(row);
+ jobs.removeAt(row);
+ setActionsEnabled();
+
+ saveChanges = true;
+ saveSettings();
+}
+
+void MainWindow::torrentStopped()
+{
+ // Schedule the client for deletion.
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ client->deleteLater();
+
+ // If the quit dialog is shown, update its progress.
+ if (quitDialog) {
+ if (++jobsStopped == jobsToStop)
+ quitDialog->close();
+ }
+}
+
+void MainWindow::torrentError(TorrentClient::Error)
+{
+ // Delete the client.
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ int row = rowOfClient(client);
+ QString fileName = jobs.at(row).torrentFileName;
+ jobs.removeAt(row);
+
+ // Display the warning.
+ QMessageBox::warning(this, tr("Error"),
+ tr("An error occurred while downloading %0: %1")
+ .arg(fileName)
+ .arg(client->errorString()));
+
+ delete torrentView->takeTopLevelItem(row);
+ client->deleteLater();
+}
+
+bool MainWindow::addTorrent(const QString &fileName, const QString &destinationFolder,
+ const QByteArray &resumeState)
+{
+ // Check if the torrent is already being downloaded.
+ foreach (Job job, jobs) {
+ if (job.torrentFileName == fileName && job.destinationDirectory == destinationFolder) {
+ QMessageBox::warning(this, tr("Already downloading"),
+ tr("The torrent file %1 is "
+ "already being downloaded.").arg(fileName));
+ return false;
+ }
+ }
+
+ // Create a new torrent client and attempt to parse the torrent data.
+ TorrentClient *client = new TorrentClient(this);
+ if (!client->setTorrent(fileName)) {
+ QMessageBox::warning(this, tr("Error"),
+ tr("The torrent file %1 cannot not be opened/resumed.").arg(fileName));
+ delete client;
+ return false;
+ }
+ client->setDestinationFolder(destinationFolder);
+ client->setDumpedState(resumeState);
+
+ // Setup the client connections.
+ connect(client, SIGNAL(stateChanged(TorrentClient::State)), this, SLOT(updateState(TorrentClient::State)));
+ connect(client, SIGNAL(peerInfoUpdated()), this, SLOT(updatePeerInfo()));
+ connect(client, SIGNAL(progressUpdated(int)), this, SLOT(updateProgress(int)));
+ connect(client, SIGNAL(downloadRateUpdated(int)), this, SLOT(updateDownloadRate(int)));
+ connect(client, SIGNAL(uploadRateUpdated(int)), this, SLOT(updateUploadRate(int)));
+ connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
+ connect(client, SIGNAL(error(TorrentClient::Error)), this, SLOT(torrentError(TorrentClient::Error)));
+
+ // Add the client to the list of downloading jobs.
+ Job job;
+ job.client = client;
+ job.torrentFileName = fileName;
+ job.destinationDirectory = destinationFolder;
+ jobs << job;
+
+ // Create and add a row in the torrent view for this download.
+ QTreeWidgetItem *item = new QTreeWidgetItem(torrentView);
+
+ QString baseFileName = QFileInfo(fileName).fileName();
+ if (baseFileName.toLower().endsWith(".torrent"))
+ baseFileName.remove(baseFileName.size() - 8);
+
+ item->setText(0, baseFileName);
+ item->setToolTip(0, tr("Torrent: %1<br>Destination: %2")
+ .arg(baseFileName).arg(destinationFolder));
+ item->setText(1, tr("0/0"));
+ item->setText(2, "0");
+ item->setText(3, "0.0 KB/s");
+ item->setText(4, "0.0 KB/s");
+ item->setText(5, tr("Idle"));
+ item->setFlags(item->flags() & ~Qt::ItemIsEditable);
+ item->setTextAlignment(1, Qt::AlignHCenter);
+
+ if (!saveChanges) {
+ saveChanges = true;
+ QTimer::singleShot(5000, this, SLOT(saveSettings()));
+ }
+ client->start();
+ return true;
+}
+
+void MainWindow::saveSettings()
+{
+ if (!saveChanges)
+ return;
+ saveChanges = false;
+
+ // Prepare and reset the settings
+ QSettings settings("Trolltech", "Torrent");
+ settings.clear();
+
+ settings.setValue("LastDirectory", lastDirectory);
+ settings.setValue("UploadLimit", uploadLimitSlider->value());
+ settings.setValue("DownloadLimit", downloadLimitSlider->value());
+
+ // Store data on all known torrents
+ settings.beginWriteArray("Torrents");
+ for (int i = 0; i < jobs.size(); ++i) {
+ settings.setArrayIndex(i);
+ settings.setValue("sourceFileName", jobs.at(i).torrentFileName);
+ settings.setValue("destinationFolder", jobs.at(i).destinationDirectory);
+ settings.setValue("uploadedBytes", jobs.at(i).client->uploadedBytes());
+ settings.setValue("downloadedBytes", jobs.at(i).client->downloadedBytes());
+ settings.setValue("resumeState", jobs.at(i).client->dumpedState());
+ }
+ settings.endArray();
+ settings.sync();
+}
+
+void MainWindow::updateState(TorrentClient::State)
+{
+ // Update the state string whenever the client's state changes.
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ int row = rowOfClient(client);
+ QTreeWidgetItem *item = torrentView->topLevelItem(row);
+ if (item) {
+ item->setToolTip(0, tr("Torrent: %1<br>Destination: %2<br>State: %3")
+ .arg(jobs.at(row).torrentFileName)
+ .arg(jobs.at(row).destinationDirectory)
+ .arg(client->stateString()));
+
+ item->setText(5, client->stateString());
+ }
+ setActionsEnabled();
+}
+
+void MainWindow::updatePeerInfo()
+{
+ // Update the number of connected, visited, seed and leecher peers.
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ int row = rowOfClient(client);
+
+ QTreeWidgetItem *item = torrentView->topLevelItem(row);
+ item->setText(1, tr("%1/%2").arg(client->connectedPeerCount())
+ .arg(client->seedCount()));
+}
+
+void MainWindow::updateProgress(int percent)
+{
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ int row = rowOfClient(client);
+
+ // Update the progressbar.
+ QTreeWidgetItem *item = torrentView->topLevelItem(row);
+ if (item)
+ item->setText(2, QString::number(percent));
+}
+
+void MainWindow::setActionsEnabled()
+{
+ // Find the view item and client for the current row, and update
+ // the states of the actions.
+ QTreeWidgetItem *item = 0;
+ if (!torrentView->selectedItems().isEmpty())
+ item = torrentView->selectedItems().first();
+ TorrentClient *client = item ? jobs.at(torrentView->indexOfTopLevelItem(item)).client : 0;
+ bool pauseEnabled = client && ((client->state() == TorrentClient::Paused)
+ || (client->state() > TorrentClient::Preparing));
+
+ removeTorrentAction->setEnabled(item != 0);
+ pauseTorrentAction->setEnabled(item != 0 && pauseEnabled);
+
+ if (client && client->state() == TorrentClient::Paused) {
+ pauseTorrentAction->setIcon(QIcon(":/icons/player_play.png"));
+ pauseTorrentAction->setText(tr("Resume torrent"));
+ } else {
+ pauseTorrentAction->setIcon(QIcon(":/icons/player_pause.png"));
+ pauseTorrentAction->setText(tr("Pause torrent"));
+ }
+
+ int row = torrentView->indexOfTopLevelItem(item);
+ upActionTool->setEnabled(item && row != 0);
+ downActionTool->setEnabled(item && row != jobs.size() - 1);
+}
+
+void MainWindow::updateDownloadRate(int bytesPerSecond)
+{
+ // Update the download rate.
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ int row = rowOfClient(client);
+ QString num;
+ num.sprintf("%.1f KB/s", bytesPerSecond / 1024.0);
+ torrentView->topLevelItem(row)->setText(3, num);
+
+ if (!saveChanges) {
+ saveChanges = true;
+ QTimer::singleShot(5000, this, SLOT(saveSettings()));
+ }
+}
+
+void MainWindow::updateUploadRate(int bytesPerSecond)
+{
+ // Update the upload rate.
+ TorrentClient *client = qobject_cast<TorrentClient *>(sender());
+ int row = rowOfClient(client);
+ QString num;
+ num.sprintf("%.1f KB/s", bytesPerSecond / 1024.0);
+ torrentView->topLevelItem(row)->setText(4, num);
+
+ if (!saveChanges) {
+ saveChanges = true;
+ QTimer::singleShot(5000, this, SLOT(saveSettings()));
+ }
+}
+
+void MainWindow::pauseTorrent()
+{
+ // Pause or unpause the current torrent.
+ int row = torrentView->indexOfTopLevelItem(torrentView->currentItem());
+ TorrentClient *client = jobs.at(row).client;
+ client->setPaused(client->state() != TorrentClient::Paused);
+ setActionsEnabled();
+}
+
+void MainWindow::moveTorrentUp()
+{
+ QTreeWidgetItem *item = torrentView->currentItem();
+ int row = torrentView->indexOfTopLevelItem(item);
+ if (row == 0)
+ return;
+
+ Job tmp = jobs.at(row - 1);
+ jobs[row - 1] = jobs[row];
+ jobs[row] = tmp;
+
+ QTreeWidgetItem *itemAbove = torrentView->takeTopLevelItem(row - 1);
+ torrentView->insertTopLevelItem(row, itemAbove);
+ setActionsEnabled();
+}
+
+void MainWindow::moveTorrentDown()
+{
+ QTreeWidgetItem *item = torrentView->currentItem();
+ int row = torrentView->indexOfTopLevelItem(item);
+ if (row == jobs.size() - 1)
+ return;
+
+ Job tmp = jobs.at(row + 1);
+ jobs[row + 1] = jobs[row];
+ jobs[row] = tmp;
+
+ QTreeWidgetItem *itemAbove = torrentView->takeTopLevelItem(row + 1);
+ torrentView->insertTopLevelItem(row, itemAbove);
+ setActionsEnabled();
+}
+
+static int rateFromValue(int value)
+{
+ int rate = 0;
+ if (value >= 0 && value < 250) {
+ rate = 1 + int(value * 0.124);
+ } else if (value < 500) {
+ rate = 32 + int((value - 250) * 0.384);
+ } else if (value < 750) {
+ rate = 128 + int((value - 500) * 1.536);
+ } else {
+ rate = 512 + int((value - 750) * 6.1445);
+ }
+ return rate;
+}
+
+void MainWindow::setUploadLimit(int value)
+{
+ int rate = rateFromValue(value);
+ uploadLimitLabel->setText(tr("%1 KB/s").arg(QString().sprintf("%4d", rate)));
+ RateController::instance()->setUploadLimit(rate * 1024);
+}
+
+void MainWindow::setDownloadLimit(int value)
+{
+ int rate = rateFromValue(value);
+ downloadLimitLabel->setText(tr("%1 KB/s").arg(QString().sprintf("%4d", rate)));
+ RateController::instance()->setDownloadLimit(rate * 1024);
+}
+
+void MainWindow::about()
+{
+ QLabel *icon = new QLabel;
+ icon->setPixmap(QPixmap(":/icons/peertopeer.png"));
+
+ QLabel *text = new QLabel;
+ text->setWordWrap(true);
+ text->setText("<p>The <b>Torrent Client</b> example demonstrates how to"
+ " write a complete peer-to-peer file sharing"
+ " application using Qt's network and thread classes.</p>"
+ "<p>This feature complete client implementation of"
+ " the BitTorrent protocol can efficiently"
+ " maintain several hundred network connections"
+ " simultaneously.</p>");
+
+ QPushButton *quitButton = new QPushButton("OK");
+
+ QHBoxLayout *topLayout = new QHBoxLayout;
+ topLayout->setMargin(10);
+ topLayout->setSpacing(10);
+ topLayout->addWidget(icon);
+ topLayout->addWidget(text);
+
+ QHBoxLayout *bottomLayout = new QHBoxLayout;
+ bottomLayout->addStretch();
+ bottomLayout->addWidget(quitButton);
+ bottomLayout->addStretch();
+
+ QVBoxLayout *mainLayout = new QVBoxLayout;
+ mainLayout->addLayout(topLayout);
+ mainLayout->addLayout(bottomLayout);
+
+ QDialog about(this);
+ about.setModal(true);
+ about.setWindowTitle(tr("About Torrent Client"));
+ about.setLayout(mainLayout);
+
+ connect(quitButton, SIGNAL(clicked()), &about, SLOT(close()));
+
+ about.exec();
+}
+
+void MainWindow::acceptFileDrop(const QString &fileName)
+{
+ // Create and show the "Add Torrent" dialog.
+ AddTorrentDialog *addTorrentDialog = new AddTorrentDialog;
+ lastDirectory = QFileInfo(fileName).absolutePath();
+ addTorrentDialog->setTorrent(fileName);
+ addTorrentDialog->deleteLater();
+ if (!addTorrentDialog->exec())
+ return;
+
+ // Add the torrent to our list of downloads.
+ addTorrent(fileName, addTorrentDialog->destinationFolder());
+ saveSettings();
+}
+
+void MainWindow::closeEvent(QCloseEvent *)
+{
+ if (jobs.isEmpty())
+ return;
+
+ // Save upload / download numbers.
+ saveSettings();
+ saveChanges = false;
+
+ quitDialog = new QProgressDialog(tr("Disconnecting from trackers"), tr("Abort"), 0, jobsToStop, this);
+
+ // Stop all clients, remove the rows from the view and wait for
+ // them to signal that they have stopped.
+ jobsToStop = 0;
+ jobsStopped = 0;
+ foreach (Job job, jobs) {
+ ++jobsToStop;
+ TorrentClient *client = job.client;
+ client->disconnect();
+ connect(client, SIGNAL(stopped()), this, SLOT(torrentStopped()));
+ client->stop();
+ delete torrentView->takeTopLevelItem(0);
+ }
+
+ if (jobsToStop > jobsStopped)
+ quitDialog->exec();
+ quitDialog->deleteLater();
+ quitDialog = 0;
+}
+
+TorrentView::TorrentView(QWidget *parent)
+ : QTreeWidget(parent)
+{
+ setAcceptDrops(true);
+}
+
+void TorrentView::dragMoveEvent(QDragMoveEvent *event)
+{
+ // Accept file actions with a '.torrent' extension.
+ QUrl url(event->mimeData()->text());
+ if (url.isValid() && url.scheme().toLower() == "file"
+ && url.path().toLower().endsWith(".torrent"))
+ event->acceptProposedAction();
+}
+
+void TorrentView::dropEvent(QDropEvent *event)
+{
+ // Accept drops if the file has a '.torrent' extension and it
+ // exists.
+ QString fileName = QUrl(event->mimeData()->text()).path();
+ if (QFile::exists(fileName) && fileName.toLower().endsWith(".torrent"))
+ emit fileDropped(fileName);
+}
+
+#include "mainwindow.moc"
diff --git a/examples/network/torrent/mainwindow.h b/examples/network/torrent/mainwindow.h
new file mode 100644
index 0000000000..0d42c91ce4
--- /dev/null
+++ b/examples/network/torrent/mainwindow.h
@@ -0,0 +1,131 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QList>
+#include <QStringList>
+#include <QMainWindow>
+
+#include "torrentclient.h"
+
+QT_BEGIN_NAMESPACE
+class QAction;
+class QCloseEvent;
+class QLabel;
+class QProgressDialog;
+class QSlider;
+QT_END_NAMESPACE
+class TorrentView;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ MainWindow(QWidget *parent = 0);
+
+ QSize sizeHint() const;
+ const TorrentClient *clientForRow(int row) const;
+
+protected:
+ void closeEvent(QCloseEvent *event);
+
+private slots:
+ void loadSettings();
+ void saveSettings();
+
+ bool addTorrent();
+ void removeTorrent();
+ void pauseTorrent();
+ void moveTorrentUp();
+ void moveTorrentDown();
+
+ void torrentStopped();
+ void torrentError(TorrentClient::Error error);
+
+ void updateState(TorrentClient::State state);
+ void updatePeerInfo();
+ void updateProgress(int percent);
+ void updateDownloadRate(int bytesPerSecond);
+ void updateUploadRate(int bytesPerSecond);
+
+ void setUploadLimit(int bytes);
+ void setDownloadLimit(int bytes);
+
+ void about();
+ void setActionsEnabled();
+ void acceptFileDrop(const QString &fileName);
+
+private:
+ int rowOfClient(TorrentClient *client) const;
+ bool addTorrent(const QString &fileName, const QString &destinationFolder,
+ const QByteArray &resumeState = QByteArray());
+
+ TorrentView *torrentView;
+ QAction *pauseTorrentAction;
+ QAction *removeTorrentAction;
+ QAction *upActionTool;
+ QAction *downActionTool;
+ QSlider *uploadLimitSlider;
+ QSlider *downloadLimitSlider;
+ QLabel *uploadLimitLabel;
+ QLabel *downloadLimitLabel;
+
+ int uploadLimit;
+ int downloadLimit;
+
+ struct Job {
+ TorrentClient *client;
+ QString torrentFileName;
+ QString destinationDirectory;
+ };
+ QList<Job> jobs;
+ int jobsStopped;
+ int jobsToStop;
+
+ QString lastDirectory;
+ QProgressDialog *quitDialog;
+
+ bool saveChanges;
+};
+
+#endif
diff --git a/examples/network/torrent/metainfo.cpp b/examples/network/torrent/metainfo.cpp
new file mode 100644
index 0000000000..ba55ebace6
--- /dev/null
+++ b/examples/network/torrent/metainfo.cpp
@@ -0,0 +1,217 @@
+/****************************************************************************
+**
+** 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 "metainfo.h"
+
+#include <QDateTime>
+#include <QMetaType>
+#include <QString>
+
+MetaInfo::MetaInfo()
+{
+ clear();
+}
+
+void MetaInfo::clear()
+{
+ errString = "Unknown error";
+ content.clear();
+ infoData.clear();
+ metaInfoMultiFiles.clear();
+ metaInfoAnnounce.clear();
+ metaInfoAnnounceList.clear();
+ metaInfoCreationDate = QDateTime();
+ metaInfoComment.clear();
+ metaInfoCreatedBy.clear();
+ metaInfoName.clear();
+ metaInfoPieceLength = 0;
+ metaInfoSha1Sums.clear();
+}
+
+bool MetaInfo::parse(const QByteArray &data)
+{
+ clear();
+ content = data;
+
+ BencodeParser parser;
+ if (!parser.parse(content)) {
+ errString = parser.errorString();
+ return false;
+ }
+
+ infoData = parser.infoSection();
+
+ QMap<QByteArray, QVariant> dict = parser.dictionary();
+ if (!dict.contains("info"))
+ return false;
+
+ QMap<QByteArray, QVariant> info = qvariant_cast<Dictionary>(dict.value("info"));
+
+ if (info.contains("files")) {
+ metaInfoFileForm = MultiFileForm;
+
+ QList<QVariant> files = info.value("files").toList();
+
+ for (int i = 0; i < files.size(); ++i) {
+ QMap<QByteArray, QVariant> file = qvariant_cast<Dictionary>(files.at(i));
+ QList<QVariant> pathElements = file.value("path").toList();
+ QByteArray path;
+ foreach (QVariant p, pathElements) {
+ if (!path.isEmpty())
+ path += "/";
+ path += p.toByteArray();
+ }
+
+ MetaInfoMultiFile multiFile;
+ multiFile.length = file.value("length").toLongLong();
+ multiFile.path = QString::fromUtf8(path);
+ multiFile.md5sum = file.value("md5sum").toByteArray();
+ metaInfoMultiFiles << multiFile;
+ }
+
+ metaInfoName = QString::fromUtf8(info.value("name").toByteArray());
+ metaInfoPieceLength = info.value("piece length").toInt();
+ QByteArray pieces = info.value("pieces").toByteArray();
+ for (int i = 0; i < pieces.size(); i += 20)
+ metaInfoSha1Sums << pieces.mid(i, 20);
+ } else if (info.contains("length")) {
+ metaInfoFileForm = SingleFileForm;
+ metaInfoSingleFile.length = info.value("length").toLongLong();
+ metaInfoSingleFile.md5sum = info.value("md5sum").toByteArray();
+ metaInfoSingleFile.name = QString::fromUtf8(info.value("name").toByteArray());
+ metaInfoSingleFile.pieceLength = info.value("piece length").toInt();
+
+ QByteArray pieces = info.value("pieces").toByteArray();
+ for (int i = 0; i < pieces.size(); i += 20)
+ metaInfoSingleFile.sha1Sums << pieces.mid(i, 20);
+ }
+
+ metaInfoAnnounce = QString::fromUtf8(dict.value("announce").toByteArray());
+
+ if (dict.contains("announce-list")) {
+ // ### unimplemented
+ }
+
+ if (dict.contains("creation date"))
+ metaInfoCreationDate.setTime_t(dict.value("creation date").toInt());
+ if (dict.contains("comment"))
+ metaInfoComment = QString::fromUtf8(dict.value("comment").toByteArray());
+ if (dict.contains("created by"))
+ metaInfoCreatedBy = QString::fromUtf8(dict.value("created by").toByteArray());
+
+ return true;
+}
+
+QByteArray MetaInfo::infoValue() const
+{
+ return infoData;
+}
+
+QString MetaInfo::errorString() const
+{
+ return errString;
+}
+
+MetaInfo::FileForm MetaInfo::fileForm() const
+{
+ return metaInfoFileForm;
+}
+
+QString MetaInfo::announceUrl() const
+{
+ return metaInfoAnnounce;
+}
+
+QStringList MetaInfo::announceList() const
+{
+ return metaInfoAnnounceList;
+}
+
+QDateTime MetaInfo::creationDate() const
+{
+ return metaInfoCreationDate;
+}
+
+QString MetaInfo::comment() const
+{
+ return metaInfoComment;
+}
+
+QString MetaInfo::createdBy() const
+{
+ return metaInfoCreatedBy;
+}
+
+MetaInfoSingleFile MetaInfo::singleFile() const
+{
+ return metaInfoSingleFile;
+}
+
+QList<MetaInfoMultiFile> MetaInfo::multiFiles() const
+{
+ return metaInfoMultiFiles;
+}
+
+QString MetaInfo::name() const
+{
+ return metaInfoName;
+}
+
+int MetaInfo::pieceLength() const
+{
+ return metaInfoPieceLength;
+}
+
+QList<QByteArray> MetaInfo::sha1Sums() const
+{
+ return metaInfoSha1Sums;
+}
+
+qint64 MetaInfo::totalSize() const
+{
+ if (fileForm() == SingleFileForm)
+ return singleFile().length;
+
+ qint64 size = 0;
+ foreach (MetaInfoMultiFile file, multiFiles())
+ size += file.length;
+ return size;
+}
diff --git a/examples/network/torrent/metainfo.h b/examples/network/torrent/metainfo.h
new file mode 100644
index 0000000000..afa824d74b
--- /dev/null
+++ b/examples/network/torrent/metainfo.h
@@ -0,0 +1,121 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef METAINFO_H
+#define METAINFO_H
+
+#include <QByteArray>
+#include <QDateTime>
+#include <QList>
+#include <QMap>
+#include <QString>
+#include <QStringList>
+#include <QVariant>
+
+struct MetaInfoSingleFile
+{
+ qint64 length;
+ QByteArray md5sum;
+ QString name;
+ int pieceLength;
+ QList<QByteArray> sha1Sums;
+};
+
+struct MetaInfoMultiFile
+{
+ qint64 length;
+ QByteArray md5sum;
+ QString path;
+};
+
+class MetaInfo
+{
+public:
+ enum FileForm {
+ SingleFileForm,
+ MultiFileForm
+ };
+
+ MetaInfo();
+ void clear();
+
+ bool parse(const QByteArray &data);
+ QString errorString() const;
+
+ QByteArray infoValue() const;
+
+ FileForm fileForm() const;
+ QString announceUrl() const;
+ QStringList announceList() const;
+ QDateTime creationDate() const;
+ QString comment() const;
+ QString createdBy() const;
+
+ // For single file form
+ MetaInfoSingleFile singleFile() const;
+
+ // For multifile form
+ QList<MetaInfoMultiFile> multiFiles() const;
+ QString name() const;
+ int pieceLength() const;
+ QList<QByteArray> sha1Sums() const;
+
+ // Total size
+ qint64 totalSize() const;
+
+private:
+ QString errString;
+ QByteArray content;
+ QByteArray infoData;
+
+ FileForm metaInfoFileForm;
+ MetaInfoSingleFile metaInfoSingleFile;
+ QList<MetaInfoMultiFile> metaInfoMultiFiles;
+ QString metaInfoAnnounce;
+ QStringList metaInfoAnnounceList;
+ QDateTime metaInfoCreationDate;
+ QString metaInfoComment;
+ QString metaInfoCreatedBy;
+ QString metaInfoName;
+ int metaInfoPieceLength;
+ QList<QByteArray> metaInfoSha1Sums;
+};
+
+#endif
diff --git a/examples/network/torrent/peerwireclient.cpp b/examples/network/torrent/peerwireclient.cpp
new file mode 100644
index 0000000000..0d9580eeff
--- /dev/null
+++ b/examples/network/torrent/peerwireclient.cpp
@@ -0,0 +1,664 @@
+/****************************************************************************
+**
+** 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 "peerwireclient.h"
+
+#include <QHostAddress>
+#include <QTimerEvent>
+
+static const int PendingRequestTimeout = 60 * 1000;
+static const int ClientTimeout = 120 * 1000;
+static const int ConnectTimeout = 60 * 1000;
+static const int KeepAliveInterval = 30 * 1000;
+static const int RateControlTimerDelay = 2000;
+static const int MinimalHeaderSize = 48;
+static const int FullHeaderSize = 68;
+static const char ProtocolId[] = "BitTorrent protocol";
+static const char ProtocolIdSize = 19;
+
+// Reads a 32bit unsigned int from data in network order.
+static inline quint32 fromNetworkData(const char *data)
+{
+ const unsigned char *udata = (const unsigned char *)data;
+ return (quint32(udata[0]) << 24)
+ | (quint32(udata[1]) << 16)
+ | (quint32(udata[2]) << 8)
+ | (quint32(udata[3]));
+}
+
+// Writes a 32bit unsigned int from num to data in network order.
+static inline void toNetworkData(quint32 num, char *data)
+{
+ unsigned char *udata = (unsigned char *)data;
+ udata[3] = (num & 0xff);
+ udata[2] = (num & 0xff00) >> 8;
+ udata[1] = (num & 0xff0000) >> 16;
+ udata[0] = (num & 0xff000000) >> 24;
+}
+
+// Constructs an unconnected PeerWire client and starts the connect timer.
+PeerWireClient::PeerWireClient(const QByteArray &peerId, QObject *parent)
+ : QTcpSocket(parent), pendingBlockSizes(0),
+ pwState(ChokingPeer | ChokedByPeer), receivedHandShake(false), gotPeerId(false),
+ sentHandShake(false), nextPacketLength(-1), pendingRequestTimer(0), invalidateTimeout(false),
+ keepAliveTimer(0), torrentPeer(0)
+{
+ memset(uploadSpeedData, 0, sizeof(uploadSpeedData));
+ memset(downloadSpeedData, 0, sizeof(downloadSpeedData));
+
+ transferSpeedTimer = startTimer(RateControlTimerDelay);
+ timeoutTimer = startTimer(ConnectTimeout);
+ peerIdString = peerId;
+
+ connect(this, SIGNAL(readyRead()), this, SIGNAL(readyToTransfer()));
+ connect(this, SIGNAL(connected()), this, SIGNAL(readyToTransfer()));
+
+ connect(&socket, SIGNAL(connected()),
+ this, SIGNAL(connected()));
+ connect(&socket, SIGNAL(readyRead()),
+ this, SIGNAL(readyRead()));
+ connect(&socket, SIGNAL(disconnected()),
+ this, SIGNAL(disconnected()));
+ connect(&socket, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SIGNAL(error(QAbstractSocket::SocketError)));
+ connect(&socket, SIGNAL(bytesWritten(qint64)),
+ this, SIGNAL(bytesWritten(qint64)));
+ connect(&socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
+ this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
+
+}
+
+// Registers the peer ID and SHA1 sum of the torrent, and initiates
+// the handshake.
+void PeerWireClient::initialize(const QByteArray &infoHash, int pieceCount)
+{
+ this->infoHash = infoHash;
+ peerPieces.resize(pieceCount);
+ if (!sentHandShake)
+ sendHandShake();
+}
+
+void PeerWireClient::setPeer(TorrentPeer *peer)
+{
+ torrentPeer = peer;
+}
+
+TorrentPeer *PeerWireClient::peer() const
+{
+ return torrentPeer;
+}
+
+QBitArray PeerWireClient::availablePieces() const
+{
+ return peerPieces;
+}
+
+QList<TorrentBlock> PeerWireClient::incomingBlocks() const
+{
+ return incoming;
+}
+
+// Sends a "choke" message, asking the peer to stop requesting blocks.
+void PeerWireClient::chokePeer()
+{
+ const char message[] = {0, 0, 0, 1, 0};
+ write(message, sizeof(message));
+ pwState |= ChokingPeer;
+
+ // After receiving a choke message, the peer will assume all
+ // pending requests are lost.
+ pendingBlocks.clear();
+ pendingBlockSizes = 0;
+}
+
+// Sends an "unchoke" message, allowing the peer to start/resume
+// requesting blocks.
+void PeerWireClient::unchokePeer()
+{
+ const char message[] = {0, 0, 0, 1, 1};
+ write(message, sizeof(message));
+ pwState &= ~ChokingPeer;
+
+ if (pendingRequestTimer)
+ killTimer(pendingRequestTimer);
+}
+
+// Sends a "keep-alive" message to prevent the peer from closing
+// the connection when there's no activity
+void PeerWireClient::sendKeepAlive()
+{
+ const char message[] = {0, 0, 0, 0};
+ write(message, sizeof(message));
+}
+
+// Sends an "interested" message, informing the peer that it has got
+// pieces that we'd like to download.
+void PeerWireClient::sendInterested()
+{
+ const char message[] = {0, 0, 0, 1, 2};
+ write(message, sizeof(message));
+ pwState |= InterestedInPeer;
+
+ // After telling the peer that we're interested, we expect to get
+ // unchoked within a certain timeframe; otherwise we'll drop the
+ // connection.
+ if (pendingRequestTimer)
+ killTimer(pendingRequestTimer);
+ pendingRequestTimer = startTimer(PendingRequestTimeout);
+}
+
+// Sends a "not interested" message, informing the peer that it does
+// not have any pieces that we'd like to download.
+void PeerWireClient::sendNotInterested()
+{
+ const char message[] = {0, 0, 0, 1, 3};
+ write(message, sizeof(message));
+ pwState &= ~InterestedInPeer;
+}
+
+// Sends a piece notification / a "have" message, informing the peer
+// that we have just downloaded a new piece.
+void PeerWireClient::sendPieceNotification(int piece)
+{
+ if (!sentHandShake)
+ sendHandShake();
+
+ char message[] = {0, 0, 0, 5, 4, 0, 0, 0, 0};
+ toNetworkData(piece, &message[5]);
+ write(message, sizeof(message));
+}
+
+// Sends the complete list of pieces that we have downloaded.
+void PeerWireClient::sendPieceList(const QBitArray &bitField)
+{
+ // The bitfield message may only be sent immediately after the
+ // handshaking sequence is completed, and before any other
+ // messages are sent.
+ if (!sentHandShake)
+ sendHandShake();
+
+ // Don't send the bitfield if it's all zeros.
+ if (bitField.count(true) == 0)
+ return;
+
+ int bitFieldSize = bitField.size();
+ int size = (bitFieldSize + 7) / 8;
+ QByteArray bits(size, '\0');
+ for (int i = 0; i < bitFieldSize; ++i) {
+ if (bitField.testBit(i)) {
+ quint32 byte = quint32(i) / 8;
+ quint32 bit = quint32(i) % 8;
+ bits[byte] = uchar(bits.at(byte)) | (1 << (7 - bit));
+ }
+ }
+
+ char message[] = {0, 0, 0, 1, 5};
+ toNetworkData(bits.size() + 1, &message[0]);
+ write(message, sizeof(message));
+ write(bits);
+}
+
+// Sends a request for a block.
+void PeerWireClient::requestBlock(int piece, int offset, int length)
+{
+ char message[] = {0, 0, 0, 1, 6};
+ toNetworkData(13, &message[0]);
+ write(message, sizeof(message));
+
+ char numbers[4 * 3];
+ toNetworkData(piece, &numbers[0]);
+ toNetworkData(offset, &numbers[4]);
+ toNetworkData(length, &numbers[8]);
+ write(numbers, sizeof(numbers));
+
+ incoming << TorrentBlock(piece, offset, length);
+
+ // After requesting a block, we expect the block to be sent by the
+ // other peer within a certain number of seconds. Otherwise, we
+ // drop the connection.
+ if (pendingRequestTimer)
+ killTimer(pendingRequestTimer);
+ pendingRequestTimer = startTimer(PendingRequestTimeout);
+}
+
+// Cancels a request for a block.
+void PeerWireClient::cancelRequest(int piece, int offset, int length)
+{
+ char message[] = {0, 0, 0, 1, 8};
+ toNetworkData(13, &message[0]);
+ write(message, sizeof(message));
+
+ char numbers[4 * 3];
+ toNetworkData(piece, &numbers[0]);
+ toNetworkData(offset, &numbers[4]);
+ toNetworkData(length, &numbers[8]);
+ write(numbers, sizeof(numbers));
+
+ incoming.removeAll(TorrentBlock(piece, offset, length));
+}
+
+// Sends a block to the peer.
+void PeerWireClient::sendBlock(int piece, int offset, const QByteArray &data)
+{
+ QByteArray block;
+
+ char message[] = {0, 0, 0, 1, 7};
+ toNetworkData(9 + data.size(), &message[0]);
+ block += QByteArray(message, sizeof(message));
+
+ char numbers[4 * 2];
+ toNetworkData(piece, &numbers[0]);
+ toNetworkData(offset, &numbers[4]);
+ block += QByteArray(numbers, sizeof(numbers));
+ block += data;
+
+ BlockInfo blockInfo;
+ blockInfo.pieceIndex = piece;
+ blockInfo.offset = offset;
+ blockInfo.length = data.size();
+ blockInfo.block = block;
+
+ pendingBlocks << blockInfo;
+ pendingBlockSizes += block.size();
+
+ if (pendingBlockSizes > 32 * 16384) {
+ chokePeer();
+ unchokePeer();
+ return;
+ }
+ emit readyToTransfer();
+}
+
+// Attempts to write 'bytes' bytes to the socket from the buffer.
+// This is used by RateController, which precisely controls how much
+// each client can write.
+qint64 PeerWireClient::writeToSocket(qint64 bytes)
+{
+ qint64 totalWritten = 0;
+ do {
+ if (outgoingBuffer.isEmpty() && !pendingBlocks.isEmpty()) {
+ BlockInfo block = pendingBlocks.takeFirst();
+ pendingBlockSizes -= block.length;
+ outgoingBuffer += block.block;
+ }
+ qint64 written = socket.write(outgoingBuffer.constData(),
+ qMin<qint64>(bytes - totalWritten, outgoingBuffer.size()));
+ if (written <= 0)
+ return totalWritten ? totalWritten : written;
+
+ totalWritten += written;
+ uploadSpeedData[0] += written;
+ outgoingBuffer.remove(0, written);
+ } while (totalWritten < bytes && (!outgoingBuffer.isEmpty() || !pendingBlocks.isEmpty()));
+
+ return totalWritten;
+}
+
+// Attempts to read at most 'bytes' bytes from the socket.
+qint64 PeerWireClient::readFromSocket(qint64 bytes)
+{
+ char buffer[1024];
+ qint64 totalRead = 0;
+ do {
+ qint64 bytesRead = socket.read(buffer, qMin<qint64>(sizeof(buffer), bytes - totalRead));
+ if (bytesRead <= 0)
+ break;
+ qint64 oldSize = incomingBuffer.size();
+ incomingBuffer.resize(oldSize + bytesRead);
+ memcpy(incomingBuffer.data() + oldSize, buffer, bytesRead);
+
+ totalRead += bytesRead;
+ } while (totalRead < bytes);
+
+ if (totalRead > 0) {
+ downloadSpeedData[0] += totalRead;
+ emit bytesReceived(totalRead);
+ processIncomingData();
+ }
+ return totalRead;
+}
+
+// Returns the average number of bytes per second this client is
+// downloading.
+qint64 PeerWireClient::downloadSpeed() const
+{
+ qint64 sum = 0;
+ for (unsigned int i = 0; i < sizeof(downloadSpeedData) / sizeof(qint64); ++i)
+ sum += downloadSpeedData[i];
+ return sum / (8 * 2);
+}
+
+// Returns the average number of bytes per second this client is
+// uploading.
+qint64 PeerWireClient::uploadSpeed() const
+{
+ qint64 sum = 0;
+ for (unsigned int i = 0; i < sizeof(uploadSpeedData) / sizeof(qint64); ++i)
+ sum += uploadSpeedData[i];
+ return sum / (8 * 2);
+}
+
+void PeerWireClient::setReadBufferSize(int size)
+{
+ socket.setReadBufferSize(size);
+}
+
+bool PeerWireClient::canTransferMore() const
+{
+ return bytesAvailable() > 0 || socket.bytesAvailable() > 0
+ || !outgoingBuffer.isEmpty() || !pendingBlocks.isEmpty();
+}
+
+void PeerWireClient::connectToHostImplementation(const QString &hostName,
+ quint16 port, OpenMode openMode)
+
+{
+ setOpenMode(openMode);
+ socket.connectToHost(hostName, port, openMode);
+}
+
+void PeerWireClient::diconnectFromHostImplementation()
+{
+ socket.disconnectFromHost();
+}
+
+void PeerWireClient::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == transferSpeedTimer) {
+ // Rotate the upload / download records.
+ for (int i = 6; i >= 0; --i) {
+ uploadSpeedData[i + 1] = uploadSpeedData[i];
+ downloadSpeedData[i + 1] = downloadSpeedData[i];
+ }
+ uploadSpeedData[0] = 0;
+ downloadSpeedData[0] = 0;
+ } else if (event->timerId() == timeoutTimer) {
+ // Disconnect if we timed out; otherwise the timeout is
+ // restarted.
+ if (invalidateTimeout) {
+ invalidateTimeout = false;
+ } else {
+ abort();
+ emit infoHashReceived(QByteArray());
+ }
+ } else if (event->timerId() == pendingRequestTimer) {
+ abort();
+ } else if (event->timerId() == keepAliveTimer) {
+ sendKeepAlive();
+ }
+ QTcpSocket::timerEvent(event);
+}
+
+// Sends the handshake to the peer.
+void PeerWireClient::sendHandShake()
+{
+ sentHandShake = true;
+
+ // Restart the timeout
+ if (timeoutTimer)
+ killTimer(timeoutTimer);
+ timeoutTimer = startTimer(ClientTimeout);
+
+ // Write the 68 byte PeerWire handshake.
+ write(&ProtocolIdSize, 1);
+ write(ProtocolId, ProtocolIdSize);
+ write(QByteArray(8, '\0'));
+ write(infoHash);
+ write(peerIdString);
+}
+
+void PeerWireClient::processIncomingData()
+{
+ invalidateTimeout = true;
+ if (!receivedHandShake) {
+ // Check that we received enough data
+ if (bytesAvailable() < MinimalHeaderSize)
+ return;
+
+ // Sanity check the protocol ID
+ QByteArray id = read(ProtocolIdSize + 1);
+ if (id.at(0) != ProtocolIdSize || !id.mid(1).startsWith(ProtocolId)) {
+ abort();
+ return;
+ }
+
+ // Discard 8 reserved bytes, then read the info hash and peer ID
+ (void) read(8);
+
+ // Read infoHash
+ QByteArray peerInfoHash = read(20);
+ if (!infoHash.isEmpty() && peerInfoHash != infoHash) {
+ abort();
+ return;
+ }
+
+ emit infoHashReceived(peerInfoHash);
+ if (infoHash.isEmpty()) {
+ abort();
+ return;
+ }
+
+ // Send handshake
+ if (!sentHandShake)
+ sendHandShake();
+ receivedHandShake = true;
+ }
+
+ // Handle delayed peer id arrival
+ if (!gotPeerId) {
+ if (bytesAvailable() < 20)
+ return;
+ gotPeerId = true;
+ if (read(20) == peerIdString) {
+ // We connected to ourself
+ abort();
+ return;
+ }
+ }
+
+ // Initialize keep-alive timer
+ if (!keepAliveTimer)
+ keepAliveTimer = startTimer(KeepAliveInterval);
+
+ do {
+ // Find the packet length
+ if (nextPacketLength == -1) {
+ if (bytesAvailable() < 4)
+ return;
+
+ char tmp[4];
+ read(tmp, sizeof(tmp));
+ nextPacketLength = fromNetworkData(tmp);
+
+ if (nextPacketLength < 0 || nextPacketLength > 200000) {
+ // Prevent DoS
+ abort();
+ return;
+ }
+ }
+
+ // KeepAlive
+ if (nextPacketLength == 0) {
+ nextPacketLength = -1;
+ continue;
+ }
+
+ // Wait with parsing until the whole packet has been received
+ if (bytesAvailable() < nextPacketLength)
+ return;
+
+ // Read the packet
+ QByteArray packet = read(nextPacketLength);
+ if (packet.size() != nextPacketLength) {
+ abort();
+ return;
+ }
+
+ switch (packet.at(0)) {
+ case ChokePacket:
+ // We have been choked.
+ pwState |= ChokedByPeer;
+ incoming.clear();
+ if (pendingRequestTimer)
+ killTimer(pendingRequestTimer);
+ emit choked();
+ break;
+ case UnchokePacket:
+ // We have been unchoked.
+ pwState &= ~ChokedByPeer;
+ emit unchoked();
+ break;
+ case InterestedPacket:
+ // The peer is interested in downloading.
+ pwState |= PeerIsInterested;
+ emit interested();
+ break;
+ case NotInterestedPacket:
+ // The peer is not interested in downloading.
+ pwState &= ~PeerIsInterested;
+ emit notInterested();
+ break;
+ case HavePacket: {
+ // The peer has a new piece available.
+ quint32 index = fromNetworkData(&packet.data()[1]);
+ if (index < quint32(peerPieces.size())) {
+ // Only accept indexes within the valid range.
+ peerPieces.setBit(int(index));
+ }
+ emit piecesAvailable(availablePieces());
+ break;
+ }
+ case BitFieldPacket:
+ // The peer has the following pieces available.
+ for (int i = 1; i < packet.size(); ++i) {
+ for (int bit = 0; bit < 8; ++bit) {
+ if (packet.at(i) & (1 << (7 - bit))) {
+ int bitIndex = int(((i - 1) * 8) + bit);
+ if (bitIndex >= 0 && bitIndex < peerPieces.size()) {
+ // Occasionally, broken clients claim to have
+ // pieces whose index is outside the valid range.
+ // The most common mistake is the index == size
+ // case.
+ peerPieces.setBit(bitIndex);
+ }
+ }
+ }
+ }
+ emit piecesAvailable(availablePieces());
+ break;
+ case RequestPacket: {
+ // The peer requests a block.
+ quint32 index = fromNetworkData(&packet.data()[1]);
+ quint32 begin = fromNetworkData(&packet.data()[5]);
+ quint32 length = fromNetworkData(&packet.data()[9]);
+ emit blockRequested(int(index), int(begin), int(length));
+ break;
+ }
+ case PiecePacket: {
+ int index = int(fromNetworkData(&packet.data()[1]));
+ int begin = int(fromNetworkData(&packet.data()[5]));
+
+ incoming.removeAll(TorrentBlock(index, begin, packet.size() - 9));
+
+ // The peer sends a block.
+ emit blockReceived(index, begin, packet.mid(9));
+
+ // Kill the pending block timer.
+ if (pendingRequestTimer) {
+ killTimer(pendingRequestTimer);
+ pendingRequestTimer = 0;
+ }
+ break;
+ }
+ case CancelPacket: {
+ // The peer cancels a block request.
+ quint32 index = fromNetworkData(&packet.data()[1]);
+ quint32 begin = fromNetworkData(&packet.data()[5]);
+ quint32 length = fromNetworkData(&packet.data()[9]);
+ for (int i = 0; i < pendingBlocks.size(); ++i) {
+ const BlockInfo &blockInfo = pendingBlocks.at(i);
+ if (blockInfo.pieceIndex == int(index)
+ && blockInfo.offset == int(begin)
+ && blockInfo.length == int(length)) {
+ pendingBlocks.removeAt(i);
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ // Unsupported packet type; just ignore it.
+ break;
+ }
+ nextPacketLength = -1;
+ } while (bytesAvailable() > 0);
+}
+
+void PeerWireClient::socketStateChanged(QAbstractSocket::SocketState state)
+{
+ setLocalAddress(socket.localAddress());
+ setLocalPort(socket.localPort());
+ setPeerName(socket.peerName());
+ setPeerAddress(socket.peerAddress());
+ setPeerPort(socket.peerPort());
+ setSocketState(state);
+}
+
+qint64 PeerWireClient::readData(char *data, qint64 size)
+{
+ int n = qMin<int>(size, incomingBuffer.size());
+ memcpy(data, incomingBuffer.constData(), n);
+ incomingBuffer.remove(0, n);
+ return n;
+}
+
+qint64 PeerWireClient::readLineData(char *data, qint64 maxlen)
+{
+ return QIODevice::readLineData(data, maxlen);
+}
+
+qint64 PeerWireClient::writeData(const char *data, qint64 size)
+{
+ int oldSize = outgoingBuffer.size();
+ outgoingBuffer.resize(oldSize + size);
+ memcpy(outgoingBuffer.data() + oldSize, data, size);
+ emit readyToTransfer();
+ return size;
+}
diff --git a/examples/network/torrent/peerwireclient.h b/examples/network/torrent/peerwireclient.h
new file mode 100644
index 0000000000..b8f8cb56e5
--- /dev/null
+++ b/examples/network/torrent/peerwireclient.h
@@ -0,0 +1,209 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef PEERWIRECLIENT_H
+#define PEERWIRECLIENT_H
+
+#include <QBitArray>
+#include <QList>
+#include <QTcpSocket>
+
+QT_BEGIN_NAMESPACE
+class QHostAddress;
+class QTimerEvent;
+QT_END_NAMESPACE
+class TorrentPeer;
+
+struct TorrentBlock
+{
+ inline TorrentBlock(int p, int o, int l)
+ : pieceIndex(p), offset(o), length(l)
+ {
+ }
+ inline bool operator==(const TorrentBlock &other) const
+ {
+ return pieceIndex == other.pieceIndex
+ && offset == other.offset
+ && length == other.length;
+ }
+
+ int pieceIndex;
+ int offset;
+ int length;
+};
+
+class PeerWireClient : public QTcpSocket
+{
+ Q_OBJECT
+
+public:
+ enum PeerWireStateFlag {
+ ChokingPeer = 0x1,
+ InterestedInPeer = 0x2,
+ ChokedByPeer = 0x4,
+ PeerIsInterested = 0x8
+ };
+ Q_DECLARE_FLAGS(PeerWireState, PeerWireStateFlag)
+
+ PeerWireClient(const QByteArray &peerId, QObject *parent = 0);
+ void initialize(const QByteArray &infoHash, int pieceCount);
+
+ void setPeer(TorrentPeer *peer);
+ TorrentPeer *peer() const;
+
+ // State
+ inline PeerWireState peerWireState() const { return pwState; }
+ QBitArray availablePieces() const;
+ QList<TorrentBlock> incomingBlocks() const;
+
+ // Protocol
+ void chokePeer();
+ void unchokePeer();
+ void sendInterested();
+ void sendKeepAlive();
+ void sendNotInterested();
+ void sendPieceNotification(int piece);
+ void sendPieceList(const QBitArray &bitField);
+ void requestBlock(int piece, int offset, int length);
+ void cancelRequest(int piece, int offset, int length);
+ void sendBlock(int piece, int offset, const QByteArray &data);
+
+ // Rate control
+ qint64 writeToSocket(qint64 bytes);
+ qint64 readFromSocket(qint64 bytes);
+ qint64 downloadSpeed() const;
+ qint64 uploadSpeed() const;
+
+ bool canTransferMore() const;
+ qint64 bytesAvailable() const { return incomingBuffer.size() + QTcpSocket::bytesAvailable(); }
+ qint64 socketBytesAvailable() const { return socket.bytesAvailable(); }
+ qint64 socketBytesToWrite() const { return socket.bytesToWrite(); }
+
+ void setReadBufferSize(int size);
+
+signals:
+ void infoHashReceived(const QByteArray &infoHash);
+ void readyToTransfer();
+
+ void choked();
+ void unchoked();
+ void interested();
+ void notInterested();
+
+ void piecesAvailable(const QBitArray &pieces);
+ void blockRequested(int pieceIndex, int begin, int length);
+ void blockReceived(int pieceIndex, int begin, const QByteArray &data);
+
+ void bytesReceived(qint64 size);
+
+protected slots:
+ void connectToHostImplementation(const QString &hostName,
+ quint16 port, OpenMode openMode = ReadWrite);
+ void diconnectFromHostImplementation();
+
+protected:
+ void timerEvent(QTimerEvent *event);
+
+ qint64 readData(char *data, qint64 maxlen);
+ qint64 readLineData(char *data, qint64 maxlen);
+ qint64 writeData(const char *data, qint64 len);
+
+private slots:
+ void sendHandShake();
+ void processIncomingData();
+ void socketStateChanged(QAbstractSocket::SocketState state);
+
+private:
+ // Data waiting to be read/written
+ QByteArray incomingBuffer;
+ QByteArray outgoingBuffer;
+
+ struct BlockInfo {
+ int pieceIndex;
+ int offset;
+ int length;
+ QByteArray block;
+ };
+ QList<BlockInfo> pendingBlocks;
+ int pendingBlockSizes;
+ QList<TorrentBlock> incoming;
+
+ enum PacketType {
+ ChokePacket = 0,
+ UnchokePacket = 1,
+ InterestedPacket = 2,
+ NotInterestedPacket = 3,
+ HavePacket = 4,
+ BitFieldPacket = 5,
+ RequestPacket = 6,
+ PiecePacket = 7,
+ CancelPacket = 8
+ };
+
+ // State
+ PeerWireState pwState;
+ bool receivedHandShake;
+ bool gotPeerId;
+ bool sentHandShake;
+ int nextPacketLength;
+
+ // Upload/download speed records
+ qint64 uploadSpeedData[8];
+ qint64 downloadSpeedData[8];
+ int transferSpeedTimer;
+
+ // Timeout handling
+ int timeoutTimer;
+ int pendingRequestTimer;
+ bool invalidateTimeout;
+ int keepAliveTimer;
+
+ // Checksum, peer ID and set of available pieces
+ QByteArray infoHash;
+ QByteArray peerIdString;
+ QBitArray peerPieces;
+ TorrentPeer *torrentPeer;
+
+ QTcpSocket socket;
+};
+
+Q_DECLARE_OPERATORS_FOR_FLAGS(PeerWireClient::PeerWireState)
+
+#endif
diff --git a/examples/network/torrent/ratecontroller.cpp b/examples/network/torrent/ratecontroller.cpp
new file mode 100644
index 0000000000..47cf177ccb
--- /dev/null
+++ b/examples/network/torrent/ratecontroller.cpp
@@ -0,0 +1,155 @@
+/****************************************************************************
+**
+** 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 "peerwireclient.h"
+#include "ratecontroller.h"
+
+#include <QtCore>
+
+Q_GLOBAL_STATIC(RateController, rateController)
+
+RateController *RateController::instance()
+{
+ return rateController();
+}
+
+void RateController::addSocket(PeerWireClient *socket)
+{
+ connect(socket, SIGNAL(readyToTransfer()), this, SLOT(scheduleTransfer()));
+ socket->setReadBufferSize(downLimit * 4);
+ sockets << socket;
+ scheduleTransfer();
+}
+
+void RateController::removeSocket(PeerWireClient *socket)
+{
+ disconnect(socket, SIGNAL(readyToTransfer()), this, SLOT(scheduleTransfer()));
+ socket->setReadBufferSize(0);
+ sockets.remove(socket);
+}
+
+void RateController::setDownloadLimit(int bytesPerSecond)
+{
+ downLimit = bytesPerSecond;
+ foreach (PeerWireClient *socket, sockets)
+ socket->setReadBufferSize(downLimit * 4);
+}
+
+void RateController::scheduleTransfer()
+{
+ if (transferScheduled)
+ return;
+ transferScheduled = true;
+ QTimer::singleShot(50, this, SLOT(transfer()));
+}
+
+void RateController::transfer()
+{
+ transferScheduled = false;
+ if (sockets.isEmpty())
+ return;
+
+ int msecs = 1000;
+ if (!stopWatch.isNull())
+ msecs = qMin(msecs, stopWatch.elapsed());
+
+ qint64 bytesToWrite = (upLimit * msecs) / 1000;
+ qint64 bytesToRead = (downLimit * msecs) / 1000;
+ if (bytesToWrite == 0 && bytesToRead == 0) {
+ scheduleTransfer();
+ return;
+ }
+
+ QSet<PeerWireClient *> pendingSockets;
+ foreach (PeerWireClient *client, sockets) {
+ if (client->canTransferMore())
+ pendingSockets << client;
+ }
+ if (pendingSockets.isEmpty())
+ return;
+
+ stopWatch.start();
+
+ bool canTransferMore;
+ do {
+ canTransferMore = false;
+ qint64 writeChunk = qMax<qint64>(1, bytesToWrite / pendingSockets.size());
+ qint64 readChunk = qMax<qint64>(1, bytesToRead / pendingSockets.size());
+
+ QSetIterator<PeerWireClient *> it(pendingSockets);
+ while (it.hasNext() && (bytesToWrite > 0 || bytesToRead > 0)) {
+ PeerWireClient *socket = it.next();
+ if (socket->state() != QAbstractSocket::ConnectedState) {
+ pendingSockets.remove(socket);
+ continue;
+ }
+
+ bool dataTransferred = false;
+ qint64 available = qMin<qint64>(socket->socketBytesAvailable(), readChunk);
+ if (available > 0) {
+ qint64 readBytes = socket->readFromSocket(qMin<qint64>(available, bytesToRead));
+ if (readBytes > 0) {
+ bytesToRead -= readBytes;
+ dataTransferred = true;
+ }
+ }
+
+ if (upLimit * 2 > socket->bytesToWrite()) {
+ qint64 chunkSize = qMin<qint64>(writeChunk, bytesToWrite);
+ qint64 toWrite = qMin(upLimit * 2 - socket->bytesToWrite(), chunkSize);
+ if (toWrite > 0) {
+ qint64 writtenBytes = socket->writeToSocket(toWrite);
+ if (writtenBytes > 0) {
+ bytesToWrite -= writtenBytes;
+ dataTransferred = true;
+ }
+ }
+ }
+
+ if (dataTransferred && socket->canTransferMore())
+ canTransferMore = true;
+ else
+ pendingSockets.remove(socket);
+ }
+ } while (canTransferMore && (bytesToWrite > 0 || bytesToRead > 0) && !pendingSockets.isEmpty());
+
+ if (canTransferMore || bytesToWrite == 0 || bytesToRead == 0)
+ scheduleTransfer();
+}
diff --git a/examples/network/torrent/ratecontroller.h b/examples/network/torrent/ratecontroller.h
new file mode 100644
index 0000000000..4949abb641
--- /dev/null
+++ b/examples/network/torrent/ratecontroller.h
@@ -0,0 +1,79 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef RATECONTROLLER_H
+#define RATECONTROLLER_H
+
+#include <QObject>
+#include <QSet>
+#include <QTime>
+
+class PeerWireClient;
+
+class RateController : public QObject
+{
+ Q_OBJECT
+
+public:
+ inline RateController(QObject *parent = 0)
+ : QObject(parent), transferScheduled(false) { }
+ static RateController *instance();
+
+ void addSocket(PeerWireClient *socket);
+ void removeSocket(PeerWireClient *socket);
+
+ inline int uploadLimit() const { return upLimit; }
+ inline int downloadLimit() const { return downLimit; }
+ inline void setUploadLimit(int bytesPerSecond) { upLimit = bytesPerSecond; }
+ void setDownloadLimit(int bytesPerSecond);
+
+public slots:
+ void transfer();
+ void scheduleTransfer();
+
+private:
+ QTime stopWatch;
+ QSet<PeerWireClient *> sockets;
+ int upLimit;
+ int downLimit;
+ bool transferScheduled;
+};
+
+#endif
diff --git a/examples/network/torrent/torrent.pro b/examples/network/torrent/torrent.pro
new file mode 100644
index 0000000000..a9acf7bbc0
--- /dev/null
+++ b/examples/network/torrent/torrent.pro
@@ -0,0 +1,39 @@
+HEADERS += addtorrentdialog.h \
+ bencodeparser.h \
+ connectionmanager.h \
+ mainwindow.h \
+ metainfo.h \
+ peerwireclient.h \
+ ratecontroller.h \
+ filemanager.h \
+ torrentclient.h \
+ torrentserver.h \
+ trackerclient.h
+
+SOURCES += main.cpp \
+ addtorrentdialog.cpp \
+ bencodeparser.cpp \
+ connectionmanager.cpp \
+ mainwindow.cpp \
+ metainfo.cpp \
+ peerwireclient.cpp \
+ ratecontroller.cpp \
+ filemanager.cpp \
+ torrentclient.cpp \
+ torrentserver.cpp \
+ trackerclient.cpp
+
+# Forms and resources
+FORMS += forms/addtorrentform.ui
+RESOURCES += icons.qrc
+
+QT += network
+
+# install
+target.path = $$[QT_INSTALL_EXAMPLES]/qtbase/network/torrent
+sources.files = $$SOURCES $$HEADERS $$RESOURCES torrent.pro *.torrent
+sources.files += icons forms 3rdparty
+sources.path = $$[QT_INSTALL_EXAMPLES]/qtbase/network/torrent
+INSTALLS += target sources
+
+symbian: include($$QT_SOURCE_TREE/examples/symbianpkgrules.pri)
diff --git a/examples/network/torrent/torrentclient.cpp b/examples/network/torrent/torrentclient.cpp
new file mode 100644
index 0000000000..3df1bb886f
--- /dev/null
+++ b/examples/network/torrent/torrentclient.cpp
@@ -0,0 +1,1528 @@
+/****************************************************************************
+**
+** 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 "connectionmanager.h"
+#include "filemanager.h"
+#include "metainfo.h"
+#include "torrentclient.h"
+#include "torrentserver.h"
+#include "trackerclient.h"
+#include "peerwireclient.h"
+#include "ratecontroller.h"
+
+#include <QtCore>
+#include <QNetworkInterface>
+
+// These constants could also be configurable by the user.
+static const int ServerMinPort = 6881;
+static const int ServerMaxPort = /* 6889 */ 7000;
+static const int BlockSize = 16384;
+static const int MaxBlocksInProgress = 5;
+static const int MaxBlocksInMultiMode = 2;
+static const int MaxConnectionPerPeer = 1;
+static const int RateControlWindowLength = 10;
+static const int RateControlTimerDelay = 1000;
+static const int MinimumTimeBeforeRevisit = 30;
+static const int MaxUploads = 4;
+static const int UploadScheduleInterval = 10000;
+static const int EndGamePieces = 5;
+
+class TorrentPiece {
+public:
+ int index;
+ int length;
+ QBitArray completedBlocks;
+ QBitArray requestedBlocks;
+ bool inProgress;
+};
+
+class TorrentClientPrivate
+{
+public:
+ TorrentClientPrivate(TorrentClient *qq);
+
+ // State / error
+ void setError(TorrentClient::Error error);
+ void setState(TorrentClient::State state);
+ TorrentClient::Error error;
+ TorrentClient::State state;
+ QString errorString;
+ QString stateString;
+
+ // Where to save data
+ QString destinationFolder;
+ MetaInfo metaInfo;
+
+ // Announce tracker and file manager
+ QByteArray peerId;
+ QByteArray infoHash;
+ TrackerClient trackerClient;
+ FileManager fileManager;
+
+ // Connections
+ QList<PeerWireClient *> connections;
+ QList<TorrentPeer *> peers;
+ bool schedulerCalled;
+ void callScheduler();
+ bool connectingToClients;
+ void callPeerConnector();
+ int uploadScheduleTimer;
+
+ // Pieces
+ QMap<int, PeerWireClient *> readIds;
+ QMultiMap<PeerWireClient *, TorrentPiece *> payloads;
+ QMap<int, TorrentPiece *> pendingPieces;
+ QBitArray completedPieces;
+ QBitArray incompletePieces;
+ int pieceCount;
+
+ // Progress
+ int lastProgressValue;
+ qint64 downloadedBytes;
+ qint64 uploadedBytes;
+ int downloadRate[RateControlWindowLength];
+ int uploadRate[RateControlWindowLength];
+ int transferRateTimer;
+
+ TorrentClient *q;
+};
+
+TorrentClientPrivate::TorrentClientPrivate(TorrentClient *qq)
+ : trackerClient(qq), q(qq)
+{
+ error = TorrentClient::UnknownError;
+ state = TorrentClient::Idle;
+ errorString = QT_TRANSLATE_NOOP(TorrentClient, "Unknown error");
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Idle");
+ schedulerCalled = false;
+ connectingToClients = false;
+ uploadScheduleTimer = 0;
+ lastProgressValue = -1;
+ pieceCount = 0;
+ downloadedBytes = 0;
+ uploadedBytes = 0;
+ memset(downloadRate, 0, sizeof(downloadRate));
+ memset(uploadRate, 0, sizeof(uploadRate));
+ transferRateTimer = 0;
+}
+
+void TorrentClientPrivate::setError(TorrentClient::Error errorCode)
+{
+ this->error = errorCode;
+ switch (error) {
+ case TorrentClient::UnknownError:
+ errorString = QT_TRANSLATE_NOOP(TorrentClient, "Unknown error");
+ break;
+ case TorrentClient::TorrentParseError:
+ errorString = QT_TRANSLATE_NOOP(TorrentClient, "Invalid torrent data");
+ break;
+ case TorrentClient::InvalidTrackerError:
+ errorString = QT_TRANSLATE_NOOP(TorrentClient, "Unable to connect to tracker");
+ break;
+ case TorrentClient::FileError:
+ errorString = QT_TRANSLATE_NOOP(TorrentClient, "File error");
+ break;
+ case TorrentClient::ServerError:
+ errorString = QT_TRANSLATE_NOOP(TorrentClient, "Unable to initialize server");
+ break;
+ }
+ emit q->error(errorCode);
+}
+
+void TorrentClientPrivate::setState(TorrentClient::State state)
+{
+ this->state = state;
+ switch (state) {
+ case TorrentClient::Idle:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Idle");
+ break;
+ case TorrentClient::Paused:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Paused");
+ break;
+ case TorrentClient::Stopping:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Stopping");
+ break;
+ case TorrentClient::Preparing:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Preparing");
+ break;
+ case TorrentClient::Searching:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Searching");
+ break;
+ case TorrentClient::Connecting:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Connecting");
+ break;
+ case TorrentClient::WarmingUp:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Warming up");
+ break;
+ case TorrentClient::Downloading:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Downloading");
+ break;
+ case TorrentClient::Endgame:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Finishing");
+ break;
+ case TorrentClient::Seeding:
+ stateString = QT_TRANSLATE_NOOP(TorrentClient, "Seeding");
+ break;
+ }
+ emit q->stateChanged(state);
+}
+
+void TorrentClientPrivate::callScheduler()
+{
+ if (!schedulerCalled) {
+ schedulerCalled = true;
+ QMetaObject::invokeMethod(q, "scheduleDownloads", Qt::QueuedConnection);
+ }
+}
+
+void TorrentClientPrivate::callPeerConnector()
+{
+ if (!connectingToClients) {
+ connectingToClients = true;
+ QTimer::singleShot(10000, q, SLOT(connectToPeers()));
+ }
+}
+
+TorrentClient::TorrentClient(QObject *parent)
+ : QObject(parent), d(new TorrentClientPrivate(this))
+{
+ // Connect the file manager
+ connect(&d->fileManager, SIGNAL(dataRead(int,int,int,QByteArray)),
+ this, SLOT(sendToPeer(int,int,int,QByteArray)));
+ connect(&d->fileManager, SIGNAL(verificationProgress(int)),
+ this, SLOT(updateProgress(int)));
+ connect(&d->fileManager, SIGNAL(verificationDone()),
+ this, SLOT(fullVerificationDone()));
+ connect(&d->fileManager, SIGNAL(pieceVerified(int,bool)),
+ this, SLOT(pieceVerified(int,bool)));
+ connect(&d->fileManager, SIGNAL(error()),
+ this, SLOT(handleFileError()));
+
+ // Connect the tracker client
+ connect(&d->trackerClient, SIGNAL(peerListUpdated(QList<TorrentPeer>)),
+ this, SLOT(addToPeerList(QList<TorrentPeer>)));
+ connect(&d->trackerClient, SIGNAL(stopped()),
+ this, SIGNAL(stopped()));
+}
+
+TorrentClient::~TorrentClient()
+{
+ qDeleteAll(d->peers);
+ qDeleteAll(d->pendingPieces);
+ delete d;
+}
+
+bool TorrentClient::setTorrent(const QString &fileName)
+{
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly) || !setTorrent(file.readAll())) {
+ d->setError(TorrentParseError);
+ return false;
+ }
+ return true;
+}
+
+bool TorrentClient::setTorrent(const QByteArray &torrentData)
+{
+ if (!d->metaInfo.parse(torrentData)) {
+ d->setError(TorrentParseError);
+ return false;
+ }
+
+ // Calculate SHA1 hash of the "info" section in the torrent
+ QByteArray infoValue = d->metaInfo.infoValue();
+ d->infoHash = QCryptographicHash::hash(infoValue, QCryptographicHash::Sha1);
+
+ return true;
+}
+
+MetaInfo TorrentClient::metaInfo() const
+{
+ return d->metaInfo;
+}
+
+void TorrentClient::setDestinationFolder(const QString &directory)
+{
+ d->destinationFolder = directory;
+}
+
+QString TorrentClient::destinationFolder() const
+{
+ return d->destinationFolder;
+}
+
+void TorrentClient::setDumpedState(const QByteArray &dumpedState)
+{
+ // Recover partially completed pieces
+ QDataStream stream(dumpedState);
+
+ quint16 version = 0;
+ stream >> version;
+ if (version != 2)
+ return;
+
+ stream >> d->completedPieces;
+
+ while (!stream.atEnd()) {
+ int index;
+ int length;
+ QBitArray completed;
+ stream >> index >> length >> completed;
+ if (stream.status() != QDataStream::Ok) {
+ d->completedPieces.clear();
+ break;
+ }
+
+ TorrentPiece *piece = new TorrentPiece;
+ piece->index = index;
+ piece->length = length;
+ piece->completedBlocks = completed;
+ piece->requestedBlocks.resize(completed.size());
+ piece->inProgress = false;
+ d->pendingPieces[index] = piece;
+ }
+}
+
+QByteArray TorrentClient::dumpedState() const
+{
+ QByteArray partials;
+ QDataStream stream(&partials, QIODevice::WriteOnly);
+
+ stream << quint16(2);
+ stream << d->completedPieces;
+
+ // Save the state of all partially downloaded pieces into a format
+ // suitable for storing in settings.
+ QMap<int, TorrentPiece *>::ConstIterator it = d->pendingPieces.constBegin();
+ while (it != d->pendingPieces.constEnd()) {
+ TorrentPiece *piece = it.value();
+ if (blocksLeftForPiece(piece) > 0 && blocksLeftForPiece(piece) < piece->completedBlocks.size()) {
+ stream << piece->index;
+ stream << piece->length;
+ stream << piece->completedBlocks;
+ }
+ ++it;
+ }
+
+ return partials;
+}
+
+qint64 TorrentClient::progress() const
+{
+ return d->lastProgressValue;
+}
+
+void TorrentClient::setDownloadedBytes(qint64 bytes)
+{
+ d->downloadedBytes = bytes;
+}
+
+qint64 TorrentClient::downloadedBytes() const
+{
+ return d->downloadedBytes;
+}
+
+void TorrentClient::setUploadedBytes(qint64 bytes)
+{
+ d->uploadedBytes = bytes;
+}
+
+qint64 TorrentClient::uploadedBytes() const
+{
+ return d->uploadedBytes;
+}
+
+int TorrentClient::connectedPeerCount() const
+{
+ int tmp = 0;
+ foreach (PeerWireClient *client, d->connections) {
+ if (client->state() == QAbstractSocket::ConnectedState)
+ ++tmp;
+ }
+ return tmp;
+}
+
+int TorrentClient::seedCount() const
+{
+ int tmp = 0;
+ foreach (PeerWireClient *client, d->connections) {
+ if (client->availablePieces().count(true) == d->pieceCount)
+ ++tmp;
+ }
+ return tmp;
+}
+
+TorrentClient::State TorrentClient::state() const
+{
+ return d->state;
+}
+
+QString TorrentClient::stateString() const
+{
+ return d->stateString;
+}
+
+TorrentClient::Error TorrentClient::error() const
+{
+ return d->error;
+}
+
+QString TorrentClient::errorString() const
+{
+ return d->errorString;
+}
+
+QByteArray TorrentClient::peerId() const
+{
+ return d->peerId;
+}
+
+QByteArray TorrentClient::infoHash() const
+{
+ return d->infoHash;
+}
+
+void TorrentClient::start()
+{
+ if (d->state != Idle)
+ return;
+
+ TorrentServer::instance()->addClient(this);
+
+ // Initialize the file manager
+ d->setState(Preparing);
+ d->fileManager.setMetaInfo(d->metaInfo);
+ d->fileManager.setDestinationFolder(d->destinationFolder);
+ d->fileManager.setCompletedPieces(d->completedPieces);
+ d->fileManager.start(QThread::LowestPriority);
+ d->fileManager.startDataVerification();
+}
+
+void TorrentClient::stop()
+{
+ if (d->state == Stopping)
+ return;
+
+ TorrentServer::instance()->removeClient(this);
+
+ // Update the state
+ State oldState = d->state;
+ d->setState(Stopping);
+
+ // Stop the timer
+ if (d->transferRateTimer) {
+ killTimer(d->transferRateTimer);
+ d->transferRateTimer = 0;
+ }
+
+ // Abort all existing connections
+ foreach (PeerWireClient *client, d->connections) {
+ RateController::instance()->removeSocket(client);
+ ConnectionManager::instance()->removeConnection(client);
+ client->abort();
+ }
+ d->connections.clear();
+
+ // Perhaps stop the tracker
+ if (oldState > Preparing) {
+ d->trackerClient.stop();
+ } else {
+ d->setState(Idle);
+ emit stopped();
+ }
+}
+
+void TorrentClient::setPaused(bool paused)
+{
+ if (paused) {
+ // Abort all connections, and set the max number of
+ // connections to 0. Keep the list of peers, so we can quickly
+ // resume later.
+ d->setState(Paused);
+ foreach (PeerWireClient *client, d->connections)
+ client->abort();
+ d->connections.clear();
+ TorrentServer::instance()->removeClient(this);
+ } else {
+ // Restore the max number of connections, and start the peer
+ // connector. We should also quickly start receiving incoming
+ // connections.
+ d->setState(d->completedPieces.count(true) == d->fileManager.pieceCount()
+ ? Seeding : Searching);
+ connectToPeers();
+ TorrentServer::instance()->addClient(this);
+ }
+}
+
+void TorrentClient::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == d->uploadScheduleTimer) {
+ // Update the state of who's choked and who's not
+ scheduleUploads();
+ return;
+ }
+
+ if (event->timerId() != d->transferRateTimer) {
+ QObject::timerEvent(event);
+ return;
+ }
+
+ // Calculate average upload/download rate
+ qint64 uploadBytesPerSecond = 0;
+ qint64 downloadBytesPerSecond = 0;
+ for (int i = 0; i < RateControlWindowLength; ++i) {
+ uploadBytesPerSecond += d->uploadRate[i];
+ downloadBytesPerSecond += d->downloadRate[i];
+ }
+ uploadBytesPerSecond /= qint64(RateControlWindowLength);
+ downloadBytesPerSecond /= qint64(RateControlWindowLength);
+ for (int i = RateControlWindowLength - 2; i >= 0; --i) {
+ d->uploadRate[i + 1] = d->uploadRate[i];
+ d->downloadRate[i + 1] = d->downloadRate[i];
+ }
+ d->uploadRate[0] = 0;
+ d->downloadRate[0] = 0;
+ emit uploadRateUpdated(int(uploadBytesPerSecond));
+ emit downloadRateUpdated(int(downloadBytesPerSecond));
+
+ // Stop the timer if there is no activity.
+ if (downloadBytesPerSecond == 0 && uploadBytesPerSecond == 0) {
+ killTimer(d->transferRateTimer);
+ d->transferRateTimer = 0;
+ }
+}
+
+void TorrentClient::sendToPeer(int readId, int pieceIndex, int begin, const QByteArray &data)
+{
+ // Send the requested block to the peer if the client connection
+ // still exists; otherwise do nothing. This slot is called by the
+ // file manager after it has read a block of data.
+ PeerWireClient *client = d->readIds.value(readId);
+ if (client) {
+ if ((client->peerWireState() & PeerWireClient::ChokingPeer) == 0)
+ client->sendBlock(pieceIndex, begin, data);
+ }
+ d->readIds.remove(readId);
+}
+
+void TorrentClient::fullVerificationDone()
+{
+ // Update our list of completed and incomplete pieces.
+ d->completedPieces = d->fileManager.completedPieces();
+ d->incompletePieces.resize(d->completedPieces.size());
+ d->pieceCount = d->completedPieces.size();
+ for (int i = 0; i < d->fileManager.pieceCount(); ++i) {
+ if (!d->completedPieces.testBit(i))
+ d->incompletePieces.setBit(i);
+ }
+
+ updateProgress();
+
+ // If the checksums show that what the dumped state thought was
+ // partial was in fact complete, then we trust the checksums.
+ QMap<int, TorrentPiece *>::Iterator it = d->pendingPieces.begin();
+ while (it != d->pendingPieces.end()) {
+ if (d->completedPieces.testBit(it.key()))
+ it = d->pendingPieces.erase(it);
+ else
+ ++it;
+ }
+
+ d->uploadScheduleTimer = startTimer(UploadScheduleInterval);
+
+ // Start the server
+ TorrentServer *server = TorrentServer::instance();
+ if (!server->isListening()) {
+ // Set up the peer wire server
+ for (int i = ServerMinPort; i <= ServerMaxPort; ++i) {
+ if (server->listen(QHostAddress::Any, i))
+ break;
+ }
+ if (!server->isListening()) {
+ d->setError(ServerError);
+ return;
+ }
+ }
+
+ d->setState(d->completedPieces.count(true) == d->pieceCount ? Seeding : Searching);
+
+ // Start the tracker client
+ d->trackerClient.start(d->metaInfo);
+}
+
+void TorrentClient::pieceVerified(int pieceIndex, bool ok)
+{
+ TorrentPiece *piece = d->pendingPieces.value(pieceIndex);
+
+ // Remove this piece from all payloads
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.begin();
+ while (it != d->payloads.end()) {
+ if (it.value()->index == pieceIndex)
+ it = d->payloads.erase(it);
+ else
+ ++it;
+ }
+
+ if (!ok) {
+ // If a piece did not pass the SHA1 check, we'll simply clear
+ // its state, and the scheduler will re-request it
+ piece->inProgress = false;
+ piece->completedBlocks.fill(false);
+ piece->requestedBlocks.fill(false);
+ d->callScheduler();
+ return;
+ }
+
+ // Update the peer list so we know who's still interesting.
+ foreach (TorrentPeer *peer, d->peers) {
+ if (!peer->interesting)
+ continue;
+ bool interesting = false;
+ for (int i = 0; i < d->pieceCount; ++i) {
+ if (peer->pieces.testBit(i) && d->incompletePieces.testBit(i)) {
+ interesting = true;
+ break;
+ }
+ }
+ peer->interesting = interesting;
+ }
+
+ // Delete the piece and update our structures.
+ delete piece;
+ d->pendingPieces.remove(pieceIndex);
+ d->completedPieces.setBit(pieceIndex);
+ d->incompletePieces.clearBit(pieceIndex);
+
+ // Notify connected peers.
+ foreach (PeerWireClient *client, d->connections) {
+ if (client->state() == QAbstractSocket::ConnectedState
+ && !client->availablePieces().testBit(pieceIndex)) {
+ client->sendPieceNotification(pieceIndex);
+ }
+ }
+
+ // Notify the tracker if we've entered Seeding status; otherwise
+ // call the scheduler.
+ int completed = d->completedPieces.count(true);
+ if (completed == d->pieceCount) {
+ if (d->state != Seeding) {
+ d->setState(Seeding);
+ d->trackerClient.startSeeding();
+ }
+ } else {
+ if (completed == 1)
+ d->setState(Downloading);
+ else if (d->incompletePieces.count(true) < 5 && d->pendingPieces.size() > d->incompletePieces.count(true))
+ d->setState(Endgame);
+ d->callScheduler();
+ }
+
+ updateProgress();
+}
+
+void TorrentClient::handleFileError()
+{
+ if (d->state == Paused)
+ return;
+ setPaused(true);
+ emit error(FileError);
+}
+
+void TorrentClient::connectToPeers()
+{
+ d->connectingToClients = false;
+
+ if (d->state == Stopping || d->state == Idle || d->state == Paused)
+ return;
+
+ if (d->state == Searching)
+ d->setState(Connecting);
+
+ // Find the list of peers we are not currently connected to, where
+ // the more interesting peers are listed more than once.
+ QList<TorrentPeer *> weighedPeers = weighedFreePeers();
+
+ // Start as many connections as we can
+ while (!weighedPeers.isEmpty() && ConnectionManager::instance()->canAddConnection()
+ && (qrand() % (ConnectionManager::instance()->maxConnections() / 2))) {
+ PeerWireClient *client = new PeerWireClient(ConnectionManager::instance()->clientId(), this);
+ RateController::instance()->addSocket(client);
+ ConnectionManager::instance()->addConnection(client);
+
+ initializeConnection(client);
+ d->connections << client;
+
+ // Pick a random peer from the list of weighed peers.
+ TorrentPeer *peer = weighedPeers.takeAt(qrand() % weighedPeers.size());
+ weighedPeers.removeAll(peer);
+ peer->connectStart = QDateTime::currentDateTime().toTime_t();
+ peer->lastVisited = peer->connectStart;
+
+ // Connect to the peer.
+ client->setPeer(peer);
+ client->connectToHost(peer->address, peer->port);
+ }
+}
+
+QList<TorrentPeer *> TorrentClient::weighedFreePeers() const
+{
+ QList<TorrentPeer *> weighedPeers;
+
+ // Generate a list of peers that we want to connect to.
+ uint now = QDateTime::currentDateTime().toTime_t();
+ QList<TorrentPeer *> freePeers;
+ QMap<QString, int> connectionsPerPeer;
+ foreach (TorrentPeer *peer, d->peers) {
+ bool busy = false;
+ foreach (PeerWireClient *client, d->connections) {
+ if (client->state() == PeerWireClient::ConnectedState
+ && client->peerAddress() == peer->address
+ && client->peerPort() == peer->port) {
+ if (++connectionsPerPeer[peer->address.toString()] >= MaxConnectionPerPeer) {
+ busy = true;
+ break;
+ }
+ }
+ }
+ if (!busy && (now - peer->lastVisited) > uint(MinimumTimeBeforeRevisit))
+ freePeers << peer;
+ }
+
+ // Nothing to connect to
+ if (freePeers.isEmpty())
+ return weighedPeers;
+
+ // Assign points based on connection speed and pieces available.
+ QList<QPair<int, TorrentPeer *> > points;
+ foreach (TorrentPeer *peer, freePeers) {
+ int tmp = 0;
+ if (peer->interesting) {
+ tmp += peer->numCompletedPieces;
+ if (d->state == Seeding)
+ tmp = d->pieceCount - tmp;
+ if (!peer->connectStart) // An unknown peer is as interesting as a seed
+ tmp += d->pieceCount;
+
+ // 1/5 of the total score for each second below 5 it takes to
+ // connect.
+ if (peer->connectTime < 5)
+ tmp += (d->pieceCount / 10) * (5 - peer->connectTime);
+ }
+ points << QPair<int, TorrentPeer *>(tmp, peer);
+ }
+ qSort(points);
+
+ // Minimize the list so the point difference is never more than 1.
+ typedef QPair<int,TorrentPeer*> PointPair;
+ QMultiMap<int, TorrentPeer *> pointMap;
+ int lowestScore = 0;
+ int lastIndex = 0;
+ foreach (PointPair point, points) {
+ if (point.first > lowestScore) {
+ lowestScore = point.first;
+ ++lastIndex;
+ }
+ pointMap.insert(lastIndex, point.second);
+ }
+
+ // Now make up a list of peers where the ones with more points are
+ // listed many times.
+ QMultiMap<int, TorrentPeer *>::ConstIterator it = pointMap.constBegin();
+ while (it != pointMap.constEnd()) {
+ for (int i = 0; i < it.key() + 1; ++i)
+ weighedPeers << it.value();
+ ++it;
+ }
+
+ return weighedPeers;
+}
+
+void TorrentClient::setupIncomingConnection(PeerWireClient *client)
+{
+ // Connect signals
+ initializeConnection(client);
+
+ // Initialize this client
+ RateController::instance()->addSocket(client);
+ d->connections << client;
+
+ client->initialize(d->infoHash, d->pieceCount);
+ client->sendPieceList(d->completedPieces);
+
+ emit peerInfoUpdated();
+
+ if (d->state == Searching || d->state == Connecting) {
+ int completed = d->completedPieces.count(true);
+ if (completed == 0)
+ d->setState(WarmingUp);
+ else if (d->incompletePieces.count(true) < 5 && d->pendingPieces.size() > d->incompletePieces.count(true))
+ d->setState(Endgame);
+ }
+
+ if (d->connections.isEmpty())
+ scheduleUploads();
+}
+
+void TorrentClient::setupOutgoingConnection()
+{
+ PeerWireClient *client = qobject_cast<PeerWireClient *>(sender());
+
+ // Update connection statistics.
+ foreach (TorrentPeer *peer, d->peers) {
+ if (peer->port == client->peerPort() && peer->address == client->peerAddress()) {
+ peer->connectTime = peer->lastVisited - peer->connectStart;
+ break;
+ }
+ }
+
+ // Send handshake and piece list
+ client->initialize(d->infoHash, d->pieceCount);
+ client->sendPieceList(d->completedPieces);
+
+ emit peerInfoUpdated();
+
+ if (d->state == Searching || d->state == Connecting) {
+ int completed = d->completedPieces.count(true);
+ if (completed == 0)
+ d->setState(WarmingUp);
+ else if (d->incompletePieces.count(true) < 5 && d->pendingPieces.size() > d->incompletePieces.count(true))
+ d->setState(Endgame);
+ }
+}
+
+void TorrentClient::initializeConnection(PeerWireClient *client)
+{
+ connect(client, SIGNAL(connected()),
+ this, SLOT(setupOutgoingConnection()));
+ connect(client, SIGNAL(disconnected()),
+ this, SLOT(removeClient()));
+ connect(client, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(removeClient()));
+ connect(client, SIGNAL(piecesAvailable(QBitArray)),
+ this, SLOT(peerPiecesAvailable(QBitArray)));
+ connect(client, SIGNAL(blockRequested(int,int,int)),
+ this, SLOT(peerRequestsBlock(int,int,int)));
+ connect(client, SIGNAL(blockReceived(int,int,QByteArray)),
+ this, SLOT(blockReceived(int,int,QByteArray)));
+ connect(client, SIGNAL(choked()),
+ this, SLOT(peerChoked()));
+ connect(client, SIGNAL(unchoked()),
+ this, SLOT(peerUnchoked()));
+ connect(client, SIGNAL(bytesWritten(qint64)),
+ this, SLOT(peerWireBytesWritten(qint64)));
+ connect(client, SIGNAL(bytesReceived(qint64)),
+ this, SLOT(peerWireBytesReceived(qint64)));
+}
+
+void TorrentClient::removeClient()
+{
+ PeerWireClient *client = static_cast<PeerWireClient *>(sender());
+
+ // Remove the host from our list of known peers if the connection
+ // failed.
+ if (client->peer() && client->error() == QAbstractSocket::ConnectionRefusedError)
+ d->peers.removeAll(client->peer());
+
+ // Remove the client from RateController and all structures.
+ RateController::instance()->removeSocket(client);
+ d->connections.removeAll(client);
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.find(client);
+ while (it != d->payloads.end() && it.key() == client) {
+ TorrentPiece *piece = it.value();
+ piece->inProgress = false;
+ piece->requestedBlocks.fill(false);
+ it = d->payloads.erase(it);
+ }
+
+ // Remove pending read requests.
+ QMapIterator<int, PeerWireClient *> it2(d->readIds);
+ while (it2.findNext(client))
+ d->readIds.remove(it2.key());
+
+ // Delete the client later.
+ disconnect(client, SIGNAL(disconnected()), this, SLOT(removeClient()));
+ client->deleteLater();
+ ConnectionManager::instance()->removeConnection(client);
+
+ emit peerInfoUpdated();
+ d->callPeerConnector();
+}
+
+void TorrentClient::peerPiecesAvailable(const QBitArray &pieces)
+{
+ PeerWireClient *client = qobject_cast<PeerWireClient *>(sender());
+
+ // Find the peer in our list of announced peers. If it's there,
+ // then we can use the piece list into to gather statistics that
+ // help us decide what peers to connect to.
+ TorrentPeer *peer = 0;
+ QList<TorrentPeer *>::Iterator it = d->peers.begin();
+ while (it != d->peers.end()) {
+ if ((*it)->address == client->peerAddress() && (*it)->port == client->peerPort()) {
+ peer = *it;
+ break;
+ }
+ ++it;
+ }
+
+ // If the peer is a seed, and we are in seeding mode, then the
+ // peer is uninteresting.
+ if (pieces.count(true) == d->pieceCount) {
+ if (peer)
+ peer->seed = true;
+ emit peerInfoUpdated();
+ if (d->state == Seeding) {
+ client->abort();
+ return;
+ } else {
+ if (peer)
+ peer->interesting = true;
+ if ((client->peerWireState() & PeerWireClient::InterestedInPeer) == 0)
+ client->sendInterested();
+ d->callScheduler();
+ return;
+ }
+ }
+
+ // Update our list of available pieces.
+ if (peer) {
+ peer->pieces = pieces;
+ peer->numCompletedPieces = pieces.count(true);
+ }
+
+ // Check for interesting pieces, and tell the peer whether we are
+ // interested or not.
+ bool interested = false;
+ int piecesSize = pieces.size();
+ for (int pieceIndex = 0; pieceIndex < piecesSize; ++pieceIndex) {
+ if (!pieces.testBit(pieceIndex))
+ continue;
+ if (!d->completedPieces.testBit(pieceIndex)) {
+ interested = true;
+ if ((client->peerWireState() & PeerWireClient::InterestedInPeer) == 0) {
+ if (peer)
+ peer->interesting = true;
+ client->sendInterested();
+ }
+
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.find(client);
+ int inProgress = 0;
+ while (it != d->payloads.end() && it.key() == client) {
+ if (it.value()->inProgress)
+ inProgress += it.value()->requestedBlocks.count(true);
+ ++it;
+ }
+ if (!inProgress)
+ d->callScheduler();
+ break;
+ }
+ }
+ if (!interested && (client->peerWireState() & PeerWireClient::InterestedInPeer)) {
+ if (peer)
+ peer->interesting = false;
+ client->sendNotInterested();
+ }
+}
+
+void TorrentClient::peerRequestsBlock(int pieceIndex, int begin, int length)
+{
+ PeerWireClient *client = qobject_cast<PeerWireClient *>(sender());
+
+ // Silently ignore requests from choked peers
+ if (client->peerWireState() & PeerWireClient::ChokingPeer)
+ return;
+
+ // Silently ignore requests for pieces we don't have.
+ if (!d->completedPieces.testBit(pieceIndex))
+ return;
+
+ // Request the block from the file manager
+ d->readIds.insert(d->fileManager.read(pieceIndex, begin, length),
+ qobject_cast<PeerWireClient *>(sender()));
+}
+
+void TorrentClient::blockReceived(int pieceIndex, int begin, const QByteArray &data)
+{
+ PeerWireClient *client = qobject_cast<PeerWireClient *>(sender());
+ if (data.size() == 0) {
+ client->abort();
+ return;
+ }
+
+ // Ignore it if we already have this block.
+ int blockBit = begin / BlockSize;
+ TorrentPiece *piece = d->pendingPieces.value(pieceIndex);
+ if (!piece || piece->completedBlocks.testBit(blockBit)) {
+ // Discard blocks that we already have, and fill up the pipeline.
+ requestMore(client);
+ return;
+ }
+
+ // If we are in warmup or endgame mode, cancel all duplicate
+ // requests for this block.
+ if (d->state == WarmingUp || d->state == Endgame) {
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.begin();
+ while (it != d->payloads.end()) {
+ PeerWireClient *otherClient = it.key();
+ if (otherClient != client && it.value()->index == pieceIndex) {
+ if (otherClient->incomingBlocks().contains(TorrentBlock(pieceIndex, begin, data.size())))
+ it.key()->cancelRequest(pieceIndex, begin, data.size());
+ }
+ ++it;
+ }
+ }
+
+ if (d->state != Downloading && d->state != Endgame && d->completedPieces.count(true) > 0)
+ d->setState(Downloading);
+
+ // Store this block
+ d->fileManager.write(pieceIndex, begin, data);
+ piece->completedBlocks.setBit(blockBit);
+ piece->requestedBlocks.clearBit(blockBit);
+
+ if (blocksLeftForPiece(piece) == 0) {
+ // Ask the file manager to verify the newly downloaded piece
+ d->fileManager.verifyPiece(piece->index);
+
+ // Remove this piece from all payloads
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.begin();
+ while (it != d->payloads.end()) {
+ if (!it.value() || it.value()->index == piece->index)
+ it = d->payloads.erase(it);
+ else
+ ++it;
+ }
+ }
+
+ // Fill up the pipeline.
+ requestMore(client);
+}
+
+void TorrentClient::peerWireBytesWritten(qint64 size)
+{
+ if (!d->transferRateTimer)
+ d->transferRateTimer = startTimer(RateControlTimerDelay);
+
+ d->uploadRate[0] += size;
+ d->uploadedBytes += size;
+ emit dataSent(size);
+}
+
+void TorrentClient::peerWireBytesReceived(qint64 size)
+{
+ if (!d->transferRateTimer)
+ d->transferRateTimer = startTimer(RateControlTimerDelay);
+
+ d->downloadRate[0] += size;
+ d->downloadedBytes += size;
+ emit dataSent(size);
+}
+
+int TorrentClient::blocksLeftForPiece(const TorrentPiece *piece) const
+{
+ int blocksLeft = 0;
+ int completedBlocksSize = piece->completedBlocks.size();
+ for (int i = 0; i < completedBlocksSize; ++i) {
+ if (!piece->completedBlocks.testBit(i))
+ ++blocksLeft;
+ }
+ return blocksLeft;
+}
+
+void TorrentClient::scheduleUploads()
+{
+ // Generate a list of clients sorted by their transfer
+ // speeds. When leeching, we sort by download speed, and when
+ // seeding, we sort by upload speed. Seeds are left out; there's
+ // no use in unchoking them.
+ QList<PeerWireClient *> allClients = d->connections;
+ QMultiMap<int, PeerWireClient *> transferSpeeds;
+ foreach (PeerWireClient *client, allClients) {
+ if (client->state() == QAbstractSocket::ConnectedState
+ && client->availablePieces().count(true) != d->pieceCount) {
+ if (d->state == Seeding) {
+ transferSpeeds.insert(client->uploadSpeed(), client);
+ } else {
+ transferSpeeds.insert(client->downloadSpeed(), client);
+ }
+ }
+ }
+
+ // Unchoke the top 'MaxUploads' downloaders (peers that we are
+ // uploading to) and choke all others.
+ int maxUploaders = MaxUploads;
+ QMapIterator<int, PeerWireClient *> it(transferSpeeds);
+ it.toBack();
+ while (it.hasPrevious()) {
+ PeerWireClient *client = it.previous().value();
+ bool interested = (client->peerWireState() & PeerWireClient::PeerIsInterested);
+
+ if (maxUploaders) {
+ allClients.removeAll(client);
+ if (client->peerWireState() & PeerWireClient::ChokingPeer)
+ client->unchokePeer();
+ --maxUploaders;
+ continue;
+ }
+
+ if ((client->peerWireState() & PeerWireClient::ChokingPeer) == 0) {
+ if ((qrand() % 10) == 0)
+ client->abort();
+ else
+ client->chokePeer();
+ allClients.removeAll(client);
+ }
+ if (!interested)
+ allClients.removeAll(client);
+ }
+
+ // Only interested peers are left in allClients. Unchoke one
+ // random peer to allow it to compete for a position among the
+ // downloaders. (This is known as an "optimistic unchoke".)
+ if (!allClients.isEmpty()) {
+ PeerWireClient *client = allClients[qrand() % allClients.size()];
+ if (client->peerWireState() & PeerWireClient::ChokingPeer)
+ client->unchokePeer();
+ }
+}
+
+void TorrentClient::scheduleDownloads()
+{
+ d->schedulerCalled = false;
+
+ if (d->state == Stopping || d->state == Paused || d->state == Idle)
+ return;
+
+ // Check what each client is doing, and assign payloads to those
+ // who are either idle or done.
+ foreach (PeerWireClient *client, d->connections)
+ schedulePieceForClient(client);
+}
+
+void TorrentClient::schedulePieceForClient(PeerWireClient *client)
+{
+ // Only schedule connected clients.
+ if (client->state() != QTcpSocket::ConnectedState)
+ return;
+
+ // The peer has choked us; try again later.
+ if (client->peerWireState() & PeerWireClient::ChokedByPeer)
+ return;
+
+ // Make a list of all the client's pending pieces, and count how
+ // many blocks have been requested.
+ QList<int> currentPieces;
+ bool somePiecesAreNotInProgress = false;
+ TorrentPiece *lastPendingPiece = 0;
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.find(client);
+ while (it != d->payloads.end() && it.key() == client) {
+ lastPendingPiece = it.value();
+ if (lastPendingPiece->inProgress) {
+ currentPieces << lastPendingPiece->index;
+ } else {
+ somePiecesAreNotInProgress = true;
+ }
+ ++it;
+ }
+
+ // Skip clients that already have too many blocks in progress.
+ if (client->incomingBlocks().size() >= ((d->state == Endgame || d->state == WarmingUp)
+ ? MaxBlocksInMultiMode : MaxBlocksInProgress))
+ return;
+
+ // If all pieces are in progress, but we haven't filled up our
+ // block requesting quota, then we need to schedule another piece.
+ if (!somePiecesAreNotInProgress || client->incomingBlocks().size() > 0)
+ lastPendingPiece = 0;
+ TorrentPiece *piece = lastPendingPiece;
+
+ // In warmup state, all clients request blocks from the same pieces.
+ if (d->state == WarmingUp && d->pendingPieces.size() >= 4) {
+ piece = d->payloads.value(client);
+ if (!piece) {
+ QList<TorrentPiece *> values = d->pendingPieces.values();
+ piece = values.value(qrand() % values.size());
+ piece->inProgress = true;
+ d->payloads.insert(client, piece);
+ }
+ if (piece->completedBlocks.count(false) == client->incomingBlocks().size())
+ return;
+ }
+
+ // If no pieces are currently in progress, schedule a new one.
+ if (!piece) {
+ // Build up a list of what pieces that we have not completed
+ // are available to this client.
+ QBitArray incompletePiecesAvailableToClient = d->incompletePieces;
+
+ // Remove all pieces that are marked as being in progress
+ // already (i.e., pieces that this or other clients are
+ // already waiting for). A special rule applies to warmup and
+ // endgame mode; there, we allow several clients to request
+ // the same piece. In endgame mode, this only applies to
+ // clients that are currently uploading (more than 1.0KB/s).
+ if ((d->state == Endgame && client->uploadSpeed() < 1024) || d->state != WarmingUp) {
+ QMap<int, TorrentPiece *>::ConstIterator it = d->pendingPieces.constBegin();
+ while (it != d->pendingPieces.constEnd()) {
+ if (it.value()->inProgress)
+ incompletePiecesAvailableToClient.clearBit(it.key());
+ ++it;
+ }
+ }
+
+ // Remove all pieces that the client cannot download.
+ incompletePiecesAvailableToClient &= client->availablePieces();
+
+ // Remove all pieces that this client has already requested.
+ foreach (int i, currentPieces)
+ incompletePiecesAvailableToClient.clearBit(i);
+
+ // Only continue if more pieces can be scheduled. If no pieces
+ // are available and no blocks are in progress, just leave
+ // the connection idle; it might become interesting later.
+ if (incompletePiecesAvailableToClient.count(true) == 0)
+ return;
+
+ // Check if any of the partially completed pieces can be
+ // recovered, and if so, pick a random one of them.
+ QList<TorrentPiece *> partialPieces;
+ QMap<int, TorrentPiece *>::ConstIterator it = d->pendingPieces.constBegin();
+ while (it != d->pendingPieces.constEnd()) {
+ TorrentPiece *tmp = it.value();
+ if (incompletePiecesAvailableToClient.testBit(it.key())) {
+ if (!tmp->inProgress || d->state == WarmingUp || d->state == Endgame) {
+ partialPieces << tmp;
+ break;
+ }
+ }
+ ++it;
+ }
+ if (!partialPieces.isEmpty())
+ piece = partialPieces.value(qrand() % partialPieces.size());
+
+ if (!piece) {
+ // Pick a random piece 3 out of 4 times; otherwise, pick either
+ // one of the most common or the least common pieces available,
+ // depending on the state we're in.
+ int pieceIndex = 0;
+ if (d->state == WarmingUp || (qrand() & 4) == 0) {
+ int *occurrences = new int[d->pieceCount];
+ memset(occurrences, 0, d->pieceCount * sizeof(int));
+
+ // Count how many of each piece are available.
+ foreach (PeerWireClient *peer, d->connections) {
+ QBitArray peerPieces = peer->availablePieces();
+ int peerPiecesSize = peerPieces.size();
+ for (int i = 0; i < peerPiecesSize; ++i) {
+ if (peerPieces.testBit(i))
+ ++occurrences[i];
+ }
+ }
+
+ // Find the rarest or most common pieces.
+ int numOccurrences = d->state == WarmingUp ? 0 : 99999;
+ QList<int> piecesReadyForDownload;
+ for (int i = 0; i < d->pieceCount; ++i) {
+ if (d->state == WarmingUp) {
+ // Add common pieces
+ if (occurrences[i] >= numOccurrences
+ && incompletePiecesAvailableToClient.testBit(i)) {
+ if (occurrences[i] > numOccurrences)
+ piecesReadyForDownload.clear();
+ piecesReadyForDownload.append(i);
+ numOccurrences = occurrences[i];
+ }
+ } else {
+ // Add rare pieces
+ if (occurrences[i] <= numOccurrences
+ && incompletePiecesAvailableToClient.testBit(i)) {
+ if (occurrences[i] < numOccurrences)
+ piecesReadyForDownload.clear();
+ piecesReadyForDownload.append(i);
+ numOccurrences = occurrences[i];
+ }
+ }
+ }
+
+ // Select one piece randomly
+ pieceIndex = piecesReadyForDownload.at(qrand() % piecesReadyForDownload.size());
+ delete [] occurrences;
+ } else {
+ // Make up a list of available piece indices, and pick
+ // a random one.
+ QList<int> values;
+ int incompletePiecesAvailableToClientSize = incompletePiecesAvailableToClient.size();
+ for (int i = 0; i < incompletePiecesAvailableToClientSize; ++i) {
+ if (incompletePiecesAvailableToClient.testBit(i))
+ values << i;
+ }
+ pieceIndex = values.at(qrand() % values.size());
+ }
+
+ // Create a new TorrentPiece and fill in all initial
+ // properties.
+ piece = new TorrentPiece;
+ piece->index = pieceIndex;
+ piece->length = d->fileManager.pieceLengthAt(pieceIndex);
+ int numBlocks = piece->length / BlockSize;
+ if (piece->length % BlockSize)
+ ++numBlocks;
+ piece->completedBlocks.resize(numBlocks);
+ piece->requestedBlocks.resize(numBlocks);
+ d->pendingPieces.insert(pieceIndex, piece);
+ }
+
+ piece->inProgress = true;
+ d->payloads.insert(client, piece);
+ }
+
+ // Request more blocks from all pending pieces.
+ requestMore(client);
+}
+
+void TorrentClient::requestMore(PeerWireClient *client)
+{
+ // Make a list of all pieces this client is currently waiting for,
+ // and count the number of blocks in progress.
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.find(client);
+ int numBlocksInProgress = client->incomingBlocks().size();
+ QList<TorrentPiece *> piecesInProgress;
+ while (it != d->payloads.end() && it.key() == client) {
+ TorrentPiece *piece = it.value();
+ if (piece->inProgress || (d->state == WarmingUp || d->state == Endgame))
+ piecesInProgress << piece;
+ ++it;
+ }
+
+ // If no pieces are in progress, call the scheduler.
+ if (piecesInProgress.isEmpty() && d->incompletePieces.count(true)) {
+ d->callScheduler();
+ return;
+ }
+
+ // If too many pieces are in progress, there's nothing to do.
+ int maxInProgress = ((d->state == Endgame || d->state == WarmingUp)
+ ? MaxBlocksInMultiMode : MaxBlocksInProgress);
+ if (numBlocksInProgress == maxInProgress)
+ return;
+
+ // Starting with the first piece that we're waiting for, request
+ // blocks until the quota is filled up.
+ foreach (TorrentPiece *piece, piecesInProgress) {
+ numBlocksInProgress += requestBlocks(client, piece, maxInProgress - numBlocksInProgress);
+ if (numBlocksInProgress == maxInProgress)
+ break;
+ }
+
+ // If we still didn't fill up the quota, we need to schedule more
+ // pieces.
+ if (numBlocksInProgress < maxInProgress && d->state != WarmingUp)
+ d->callScheduler();
+}
+
+int TorrentClient::requestBlocks(PeerWireClient *client, TorrentPiece *piece, int maxBlocks)
+{
+ // Generate the list of incomplete blocks for this piece.
+ QVector<int> bits;
+ int completedBlocksSize = piece->completedBlocks.size();
+ for (int i = 0; i < completedBlocksSize; ++i) {
+ if (!piece->completedBlocks.testBit(i) && !piece->requestedBlocks.testBit(i))
+ bits << i;
+ }
+
+ // Nothing more to request.
+ if (bits.size() == 0) {
+ if (d->state != WarmingUp && d->state != Endgame)
+ return 0;
+ bits.clear();
+ for (int i = 0; i < completedBlocksSize; ++i) {
+ if (!piece->completedBlocks.testBit(i))
+ bits << i;
+ }
+ }
+
+ if (d->state == WarmingUp || d->state == Endgame) {
+ // By randomizing the list of blocks to request, we
+ // significantly speed up the warmup and endgame modes, where
+ // the same blocks are requested from multiple peers. The
+ // speedup comes from an increased chance of receiving
+ // different blocks from the different peers.
+ for (int i = 0; i < bits.size(); ++i) {
+ int a = qrand() % bits.size();
+ int b = qrand() % bits.size();
+ int tmp = bits[a];
+ bits[a] = bits[b];
+ bits[b] = tmp;
+ }
+ }
+
+ // Request no more blocks than we've been asked to.
+ int blocksToRequest = qMin(maxBlocks, bits.size());
+
+ // Calculate the offset and size of each block, and send requests.
+ for (int i = 0; i < blocksToRequest; ++i) {
+ int blockSize = BlockSize;
+ if ((piece->length % BlockSize) && bits.at(i) == completedBlocksSize - 1)
+ blockSize = piece->length % BlockSize;
+ client->requestBlock(piece->index, bits.at(i) * BlockSize, blockSize);
+ piece->requestedBlocks.setBit(bits.at(i));
+ }
+
+ return blocksToRequest;
+}
+
+void TorrentClient::peerChoked()
+{
+ PeerWireClient *client = qobject_cast<PeerWireClient *>(sender());
+ if (!client)
+ return;
+
+ // When the peer chokes us, we immediately forget about all blocks
+ // we've requested from it. We also remove the piece from out
+ // payload, making it available to other clients.
+ QMultiMap<PeerWireClient *, TorrentPiece *>::Iterator it = d->payloads.find(client);
+ while (it != d->payloads.end() && it.key() == client) {
+ it.value()->inProgress = false;
+ it.value()->requestedBlocks.fill(false);
+ it = d->payloads.erase(it);
+ }
+}
+
+void TorrentClient::peerUnchoked()
+{
+ PeerWireClient *client = qobject_cast<PeerWireClient *>(sender());
+ if (!client)
+ return;
+
+ // We got unchoked, which means we can request more blocks.
+ if (d->state != Seeding)
+ d->callScheduler();
+}
+
+void TorrentClient::addToPeerList(const QList<TorrentPeer> &peerList)
+{
+ // Add peers we don't already know of to our list of peers.
+ QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
+ foreach (TorrentPeer peer, peerList) {
+ if (addresses.contains(peer.address)
+ && peer.port == TorrentServer::instance()->serverPort()) {
+ // Skip our own server.
+ continue;
+ }
+
+ bool known = false;
+ foreach (TorrentPeer *knownPeer, d->peers) {
+ if (knownPeer->port == peer.port
+ && knownPeer->address == peer.address) {
+ known = true;
+ break;
+ }
+ }
+ if (!known) {
+ TorrentPeer *newPeer = new TorrentPeer;
+ *newPeer = peer;
+ newPeer->interesting = true;
+ newPeer->seed = false;
+ newPeer->lastVisited = 0;
+ newPeer->connectStart = 0;
+ newPeer->connectTime = 999999;
+ newPeer->pieces.resize(d->pieceCount);
+ newPeer->numCompletedPieces = 0;
+ d->peers << newPeer;
+ }
+ }
+
+ // If we've got more peers than we can connect to, we remove some
+ // of the peers that have no (or low) activity.
+ int maxPeers = ConnectionManager::instance()->maxConnections() * 3;
+ if (d->peers.size() > maxPeers) {
+ // Find what peers are currently connected & active
+ QSet<TorrentPeer *> activePeers;
+ foreach (TorrentPeer *peer, d->peers) {
+ foreach (PeerWireClient *client, d->connections) {
+ if (client->peer() == peer && (client->downloadSpeed() + client->uploadSpeed()) > 1024)
+ activePeers << peer;
+ }
+ }
+
+ // Remove inactive peers from the peer list until we're below
+ // the max connections count.
+ QList<int> toRemove;
+ for (int i = 0; i < d->peers.size() && (d->peers.size() - toRemove.size()) > maxPeers; ++i) {
+ if (!activePeers.contains(d->peers.at(i)))
+ toRemove << i;
+ }
+ QListIterator<int> toRemoveIterator(toRemove);
+ toRemoveIterator.toBack();
+ while (toRemoveIterator.hasPrevious())
+ d->peers.removeAt(toRemoveIterator.previous());
+
+ // If we still have too many peers, remove the oldest ones.
+ while (d->peers.size() > maxPeers)
+ d->peers.takeFirst();
+ }
+
+ if (d->state != Paused && d->state != Stopping && d->state != Idle) {
+ if (d->state == Searching || d->state == WarmingUp)
+ connectToPeers();
+ else
+ d->callPeerConnector();
+ }
+}
+
+void TorrentClient::trackerStopped()
+{
+ d->setState(Idle);
+ emit stopped();
+}
+
+void TorrentClient::updateProgress(int progress)
+{
+ if (progress == -1 && d->pieceCount > 0) {
+ int newProgress = (d->completedPieces.count(true) * 100) / d->pieceCount;
+ if (d->lastProgressValue != newProgress) {
+ d->lastProgressValue = newProgress;
+ emit progressUpdated(newProgress);
+ }
+ } else if (d->lastProgressValue != progress) {
+ d->lastProgressValue = progress;
+ emit progressUpdated(progress);
+ }
+}
diff --git a/examples/network/torrent/torrentclient.h b/examples/network/torrent/torrentclient.h
new file mode 100644
index 0000000000..7978e8e1f4
--- /dev/null
+++ b/examples/network/torrent/torrentclient.h
@@ -0,0 +1,204 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef TORRENTCLIENT_H
+#define TORRENTCLIENT_H
+
+#include <QBitArray>
+#include <QHostAddress>
+#include <QList>
+
+class MetaInfo;
+class PeerWireClient;
+class TorrentClientPrivate;
+class TorrentPeer;
+class TorrentPiece;
+QT_BEGIN_NAMESPACE
+class QTimerEvent;
+QT_END_NAMESPACE
+
+class TorrentPeer {
+public:
+ QHostAddress address;
+ quint16 port;
+ QString id;
+ bool interesting;
+ bool seed;
+ uint lastVisited;
+ uint connectStart;
+ uint connectTime;
+ QBitArray pieces;
+ int numCompletedPieces;
+
+ inline bool operator==(const TorrentPeer &other)
+ {
+ return port == other.port
+ && address == other.address
+ && id == other.id;
+ }
+};
+
+class TorrentClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum State {
+ Idle,
+ Paused,
+ Stopping,
+ Preparing,
+ Searching,
+ Connecting,
+ WarmingUp,
+ Downloading,
+ Endgame,
+ Seeding
+ };
+ enum Error {
+ UnknownError,
+ TorrentParseError,
+ InvalidTrackerError,
+ FileError,
+ ServerError
+ };
+
+ TorrentClient(QObject *parent = 0);
+ ~TorrentClient();
+
+ bool setTorrent(const QString &fileName);
+ bool setTorrent(const QByteArray &torrentData);
+ MetaInfo metaInfo() const;
+
+ void setMaxConnections(int connections);
+ int maxConnections() const;
+
+ void setDestinationFolder(const QString &directory);
+ QString destinationFolder() const;
+
+ void setDumpedState(const QByteArray &dumpedState);
+ QByteArray dumpedState() const;
+
+ // Progress and stats for download feedback.
+ qint64 progress() const;
+ void setDownloadedBytes(qint64 bytes);
+ qint64 downloadedBytes() const;
+ void setUploadedBytes(qint64 bytes);
+ qint64 uploadedBytes() const;
+ int connectedPeerCount() const;
+ int seedCount() const;
+
+ // Accessors for the tracker
+ QByteArray peerId() const;
+ QByteArray infoHash() const;
+ quint16 serverPort() const;
+
+ // State and error.
+ State state() const;
+ QString stateString() const;
+ Error error() const;
+ QString errorString() const;
+
+signals:
+ void stateChanged(TorrentClient::State state);
+ void error(TorrentClient::Error error);
+
+ void downloadCompleted();
+ void peerInfoUpdated();
+
+ void dataSent(int uploadedBytes);
+ void dataReceived(int downloadedBytes);
+ void progressUpdated(int percentProgress);
+ void downloadRateUpdated(int bytesPerSecond);
+ void uploadRateUpdated(int bytesPerSecond);
+
+ void stopped();
+
+public slots:
+ void start();
+ void stop();
+ void setPaused(bool paused);
+ void setupIncomingConnection(PeerWireClient *client);
+
+protected slots:
+ void timerEvent(QTimerEvent *event);
+
+private slots:
+ // File management
+ void sendToPeer(int readId, int pieceIndex, int begin, const QByteArray &data);
+ void fullVerificationDone();
+ void pieceVerified(int pieceIndex, bool ok);
+ void handleFileError();
+
+ // Connection handling
+ void connectToPeers();
+ QList<TorrentPeer *> weighedFreePeers() const;
+ void setupOutgoingConnection();
+ void initializeConnection(PeerWireClient *client);
+ void removeClient();
+ void peerPiecesAvailable(const QBitArray &pieces);
+ void peerRequestsBlock(int pieceIndex, int begin, int length);
+ void blockReceived(int pieceIndex, int begin, const QByteArray &data);
+ void peerWireBytesWritten(qint64 bytes);
+ void peerWireBytesReceived(qint64 bytes);
+ int blocksLeftForPiece(const TorrentPiece *piece) const;
+
+ // Scheduling
+ void scheduleUploads();
+ void scheduleDownloads();
+ void schedulePieceForClient(PeerWireClient *client);
+ void requestMore(PeerWireClient *client);
+ int requestBlocks(PeerWireClient *client, TorrentPiece *piece, int maxBlocks);
+ void peerChoked();
+ void peerUnchoked();
+
+ // Tracker handling
+ void addToPeerList(const QList<TorrentPeer> &peerList);
+ void trackerStopped();
+
+ // Progress
+ void updateProgress(int progress = -1);
+
+private:
+ TorrentClientPrivate *d;
+ friend class TorrentClientPrivate;
+};
+
+#endif
diff --git a/examples/network/torrent/torrentserver.cpp b/examples/network/torrent/torrentserver.cpp
new file mode 100644
index 0000000000..7a9ff67972
--- /dev/null
+++ b/examples/network/torrent/torrentserver.cpp
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** 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 "connectionmanager.h"
+#include "peerwireclient.h"
+#include "ratecontroller.h"
+#include "torrentclient.h"
+#include "torrentserver.h"
+
+Q_GLOBAL_STATIC(TorrentServer, torrentServer)
+
+TorrentServer *TorrentServer::instance()
+{
+ return torrentServer();
+}
+
+void TorrentServer::addClient(TorrentClient *client)
+{
+ clients << client;
+}
+
+void TorrentServer::removeClient(TorrentClient *client)
+{
+ clients.removeAll(client);
+}
+
+void TorrentServer::incomingConnection(int socketDescriptor)
+{
+ PeerWireClient *client =
+ new PeerWireClient(ConnectionManager::instance()->clientId(), this);
+
+ if (client->setSocketDescriptor(socketDescriptor)) {
+ if (ConnectionManager::instance()->canAddConnection() && !clients.isEmpty()) {
+ connect(client, SIGNAL(infoHashReceived(QByteArray)),
+ this, SLOT(processInfoHash(QByteArray)));
+ connect(client, SIGNAL(error(QAbstractSocket::SocketError)),
+ this, SLOT(removeClient()));
+ RateController::instance()->addSocket(client);
+ ConnectionManager::instance()->addConnection(client);
+ return;
+ }
+ }
+ client->abort();
+ delete client;
+}
+
+void TorrentServer::removeClient()
+{
+ PeerWireClient *peer = qobject_cast<PeerWireClient *>(sender());
+ RateController::instance()->removeSocket(peer);
+ ConnectionManager::instance()->removeConnection(peer);
+ peer->deleteLater();
+}
+
+void TorrentServer::processInfoHash(const QByteArray &infoHash)
+{
+ PeerWireClient *peer = qobject_cast<PeerWireClient *>(sender());
+ foreach (TorrentClient *client, clients) {
+ if (client->state() >= TorrentClient::Searching && client->infoHash() == infoHash) {
+ peer->disconnect(peer, 0, this, 0);
+ client->setupIncomingConnection(peer);
+ return;
+ }
+ }
+ removeClient();
+}
diff --git a/examples/network/torrent/torrentserver.h b/examples/network/torrent/torrentserver.h
new file mode 100644
index 0000000000..2622384cfd
--- /dev/null
+++ b/examples/network/torrent/torrentserver.h
@@ -0,0 +1,71 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef TORRENTSERVER_H
+#define TORRENTSERVER_H
+
+#include <QList>
+#include <QTcpServer>
+
+class TorrentClient;
+
+class TorrentServer : public QTcpServer
+{
+ Q_OBJECT
+
+public:
+ inline TorrentServer() {}
+ static TorrentServer *instance();
+
+ void addClient(TorrentClient *client);
+ void removeClient(TorrentClient *client);
+
+protected:
+ void incomingConnection(int socketDescriptor);
+
+private slots:
+ void removeClient();
+ void processInfoHash(const QByteArray &infoHash);
+
+private:
+ QList<TorrentClient *> clients;
+};
+
+#endif
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);
+ }
+}
diff --git a/examples/network/torrent/trackerclient.h b/examples/network/torrent/trackerclient.h
new file mode 100644
index 0000000000..de63a8f87f
--- /dev/null
+++ b/examples/network/torrent/trackerclient.h
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#ifndef TRACKERCLIENT_H
+#define TRACKERCLIENT_H
+
+#include <QByteArray>
+#include <QList>
+#include <QObject>
+#include <QHostAddress>
+#include <QHttp>
+
+#include "metainfo.h"
+#include "torrentclient.h"
+
+class TorrentClient;
+
+class TrackerClient : public QObject
+{
+ Q_OBJECT
+
+public:
+ TrackerClient(TorrentClient *downloader, QObject *parent = 0);
+
+ void start(const MetaInfo &info);
+ void stop();
+ void startSeeding();
+
+signals:
+ void connectionError(QHttp::Error error);
+
+ void failure(const QString &reason);
+ void warning(const QString &message);
+ void peerListUpdated(const QList<TorrentPeer> &peerList);
+
+ void uploadCountUpdated(qint64 newUploadCount);
+ void downloadCountUpdated(qint64 newDownloadCount);
+
+ void stopped();
+
+protected:
+ void timerEvent(QTimerEvent *event);
+
+private slots:
+ void fetchPeerList();
+ void httpRequestDone(bool error);
+
+private:
+ TorrentClient *torrentDownloader;
+
+ int requestInterval;
+ int requestIntervalTimer;
+ QHttp http;
+ MetaInfo metaInfo;
+ QByteArray trackerId;
+ QList<TorrentPeer> peers;
+ qint64 uploadedBytes;
+ qint64 downloadedBytes;
+ qint64 length;
+
+ bool firstTrackerRequest;
+ bool lastTrackerRequest;
+ bool firstSeeding;
+};
+
+#endif