summaryrefslogtreecommitdiffstats
path: root/examples/network/torrent
diff options
context:
space:
mode:
Diffstat (limited to 'examples/network/torrent')
-rw-r--r--examples/network/torrent/addtorrentdialog.cpp170
-rw-r--r--examples/network/torrent/addtorrentdialog.h74
-rw-r--r--examples/network/torrent/bencodeparser.cpp235
-rw-r--r--examples/network/torrent/bencodeparser.h81
-rw-r--r--examples/network/torrent/connectionmanager.cpp89
-rw-r--r--examples/network/torrent/connectionmanager.h66
-rw-r--r--examples/network/torrent/filemanager.cpp447
-rw-r--r--examples/network/torrent/filemanager.h144
-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.cpp713
-rw-r--r--examples/network/torrent/mainwindow.h132
-rw-r--r--examples/network/torrent/metainfo.cpp218
-rw-r--r--examples/network/torrent/metainfo.h122
-rw-r--r--examples/network/torrent/peerwireclient.cpp665
-rw-r--r--examples/network/torrent/peerwireclient.h210
-rw-r--r--examples/network/torrent/ratecontroller.cpp156
-rw-r--r--examples/network/torrent/ratecontroller.h80
-rw-r--r--examples/network/torrent/torrent.pro37
-rw-r--r--examples/network/torrent/torrentclient.cpp1529
-rw-r--r--examples/network/torrent/torrentclient.h205
-rw-r--r--examples/network/torrent/torrentserver.cpp104
-rw-r--r--examples/network/torrent/torrentserver.h72
-rw-r--r--examples/network/torrent/trackerclient.cpp237
-rw-r--r--examples/network/torrent/trackerclient.h104
37 files changed, 6225 insertions, 0 deletions
diff --git a/examples/network/torrent/addtorrentdialog.cpp b/examples/network/torrent/addtorrentdialog.cpp
new file mode 100644
index 0000000..fb43f59
--- /dev/null
+++ b/examples/network/torrent/addtorrentdialog.cpp
@@ -0,0 +1,170 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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(const QString &)),
+ this, SLOT(setTorrent(const 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 0000000..89cb77c
--- /dev/null
+++ b/examples/network/torrent/addtorrentdialog.h
@@ -0,0 +1,74 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..9311c0c
--- /dev/null
+++ b/examples/network/torrent/bencodeparser.cpp
@@ -0,0 +1,235 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 << qVariantFromValue<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, qVariantFromValue<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 0000000..b4fc74b
--- /dev/null
+++ b/examples/network/torrent/bencodeparser.h
@@ -0,0 +1,81 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..3b78537
--- /dev/null
+++ b/examples/network/torrent/connectionmanager.cpp
@@ -0,0 +1,89 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..3b791e3
--- /dev/null
+++ b/examples/network/torrent/connectionmanager.h
@@ -0,0 +1,66 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..07d5cca
--- /dev/null
+++ b/examples/network/torrent/filemanager.cpp
@@ -0,0 +1,447 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..05d1f49
--- /dev/null
+++ b/examples/network/torrent/filemanager.h
@@ -0,0 +1,144 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..950bb67
--- /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 0000000..9541ef7
--- /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 0000000..08403b8
--- /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 0000000..f044811
--- /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 0000000..fe66b5d
--- /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 0000000..85b022e
--- /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 0000000..93361f5
--- /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 0000000..2f7ff43
--- /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 0000000..f4856dc
--- /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 0000000..8c9bcc4
--- /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 0000000..70daa33
--- /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 0000000..ce6585a
--- /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 0000000..52e593a
--- /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 0000000..3821254
--- /dev/null
+++ b/examples/network/torrent/main.cpp
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+#include <QApplication>
+#include <QtCore>
+
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication app(argc, argv);
+ 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 0000000..6ca3247
--- /dev/null
+++ b/examples/network/torrent/mainwindow.cpp
@@ -0,0 +1,713 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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(const QString &)),
+ this, SLOT(acceptFileDrop(const 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 0000000..3249781
--- /dev/null
+++ b/examples/network/torrent/mainwindow.h
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..03d824c
--- /dev/null
+++ b/examples/network/torrent/metainfo.cpp
@@ -0,0 +1,218 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 = qVariantValue<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 = qVariantValue<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 0000000..de60b99
--- /dev/null
+++ b/examples/network/torrent/metainfo.h
@@ -0,0 +1,122 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..e79184b
--- /dev/null
+++ b/examples/network/torrent/peerwireclient.cpp
@@ -0,0 +1,665 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..a21faa2
--- /dev/null
+++ b/examples/network/torrent/peerwireclient.h
@@ -0,0 +1,210 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..243d897
--- /dev/null
+++ b/examples/network/torrent/ratecontroller.cpp
@@ -0,0 +1,156 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..fb22159
--- /dev/null
+++ b/examples/network/torrent/ratecontroller.h
@@ -0,0 +1,80 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..10b2672
--- /dev/null
+++ b/examples/network/torrent/torrent.pro
@@ -0,0 +1,37 @@
+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]/network/torrent
+sources.files = $$SOURCES $$HEADERS $$RESOURCES torrent.pro *.torrent
+sources.files += icons forms 3rdparty
+sources.path = $$[QT_INSTALL_EXAMPLES]/network/torrent
+INSTALLS += target sources
diff --git a/examples/network/torrent/torrentclient.cpp b/examples/network/torrent/torrentclient.cpp
new file mode 100644
index 0000000..e8473bb
--- /dev/null
+++ b/examples/network/torrent/torrentclient.cpp
@@ -0,0 +1,1529 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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, const QByteArray &)),
+ this, SLOT(sendToPeer(int, int, int, const 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(const QList<TorrentPeer> &)),
+ this, SLOT(addToPeerList(const 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(const QBitArray &)),
+ this, SLOT(peerPiecesAvailable(const QBitArray &)));
+ connect(client, SIGNAL(blockRequested(int, int, int)),
+ this, SLOT(peerRequestsBlock(int, int, int)));
+ connect(client, SIGNAL(blockReceived(int, int, const QByteArray &)),
+ this, SLOT(blockReceived(int, int, const 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 *occurrances = new int[d->pieceCount];
+ memset(occurrances, 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))
+ ++occurrances[i];
+ }
+ }
+
+ // Find the rarest or most common pieces.
+ int numOccurrances = d->state == WarmingUp ? 0 : 99999;
+ QList<int> piecesReadyForDownload;
+ for (int i = 0; i < d->pieceCount; ++i) {
+ if (d->state == WarmingUp) {
+ // Add common pieces
+ if (occurrances[i] >= numOccurrances
+ && incompletePiecesAvailableToClient.testBit(i)) {
+ if (occurrances[i] > numOccurrances)
+ piecesReadyForDownload.clear();
+ piecesReadyForDownload.append(i);
+ numOccurrances = occurrances[i];
+ }
+ } else {
+ // Add rare pieces
+ if (occurrances[i] <= numOccurrances
+ && incompletePiecesAvailableToClient.testBit(i)) {
+ if (occurrances[i] < numOccurrances)
+ piecesReadyForDownload.clear();
+ piecesReadyForDownload.append(i);
+ numOccurrances = occurrances[i];
+ }
+ }
+ }
+
+ // Select one piece randomly
+ pieceIndex = piecesReadyForDownload.at(qrand() % piecesReadyForDownload.size());
+ delete [] occurrances;
+ } 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 0000000..6caaf38
--- /dev/null
+++ b/examples/network/torrent/torrentclient.h
@@ -0,0 +1,205 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..3087d6a
--- /dev/null
+++ b/examples/network/torrent/torrentserver.cpp
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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(const QByteArray &)),
+ this, SLOT(processInfoHash(const 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 0000000..6ae89e1
--- /dev/null
+++ b/examples/network/torrent/torrentserver.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 0000000..bda8140
--- /dev/null
+++ b/examples/network/torrent/trackerclient.cpp
@@ -0,0 +1,237 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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 = qVariantValue<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 0000000..f1ce37db
--- /dev/null
+++ b/examples/network/torrent/trackerclient.h
@@ -0,0 +1,104 @@
+/****************************************************************************
+**
+** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: Qt Software Information (qt-info@nokia.com)
+**
+** This file is part of the examples of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** No Commercial Usage
+** This file contains pre-release code and may not be distributed.
+** You may use this file in accordance with the terms and conditions
+** contained in the either Technology Preview License Agreement or the
+** Beta Release License Agreement.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain
+** additional rights. These rights are described in the Nokia Qt LGPL
+** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
+** package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at qt-sales@nokia.com.
+** $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