<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Qt 4.2: torrentclient.cpp Example File (network/torrent/torrentclient.cpp)</title> <link href="classic.css" rel="stylesheet" type="text/css" /> </head> <body> <table border="0" cellpadding="0" cellspacing="0" width="100%"> <tr> <td align="left" valign="top" width="32"><a href="http://www.trolltech.com/products/qt"><img src="images/qt-logo.png" align="left" width="32" height="32" border="0" /></a></td> <td width="1"> </td><td class="postheader" valign="center"><a href="index.html"><font color="#004faf">Home</font></a> · <a href="classes.html"><font color="#004faf">All Classes</font></a> · <a href="mainclasses.html"><font color="#004faf">Main Classes</font></a> · <a href="groups.html"><font color="#004faf">Grouped Classes</font></a> · <a href="modules.html"><font color="#004faf">Modules</font></a> · <a href="functions.html"><font color="#004faf">Functions</font></a></td> <td align="right" valign="top" width="230"><a href="http://www.trolltech.com"><img src="images/trolltech-logo.png" align="right" width="203" height="32" border="0" /></a></td></tr></table><h1 align="center">torrentclient.cpp Example File<br /><sup><sup>network/torrent/torrentclient.cpp</sup></sup></h1> <pre> /**************************************************************************** ** ** Copyright (C) 2004-2006 Trolltech ASA. All rights reserved. ** ** This file is part of the example classes of the Qt Toolkit. ** ** This file may be used under the terms of the GNU General Public ** License version 2.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 GNU ** General Public Licensing requirements will be met: ** http:<span class="comment">//www.trolltech.com/products/qt/opensource.html</span> ** ** If you are unsure which license is appropriate for your use, please ** review the following information: ** http:<span class="comment">//www.trolltech.com/products/qt/licensing.html or contact the</span> ** sales department at sales@trolltech.com. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ****************************************************************************/ #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> extern "C" { #include "3rdparty/sha1.h" } <span class="comment"> // These constants could also be configurable by the user.</span> 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); <span class="comment">// State / error</span> void setError(TorrentClient::Error error); void setState(TorrentClient::State state); TorrentClient::Error error; TorrentClient::State state; QString errorString; QString stateString; <span class="comment">// Where to save data</span> QString destinationFolder; MetaInfo metaInfo; <span class="comment">// Announce tracker and file manager</span> QByteArray peerId; QByteArray infoHash; TrackerClient trackerClient; FileManager fileManager; <span class="comment">// Connections</span> QList<PeerWireClient *> connections; QList<TorrentPeer *> peers; bool schedulerCalled; void callScheduler(); bool connectingToClients; void callPeerConnector(); int uploadScheduleTimer; <span class="comment">// Pieces</span> QMap<int, PeerWireClient *> readIds; QMultiMap<PeerWireClient *, TorrentPiece *> payloads; QMap<int, TorrentPiece *> pendingPieces; QBitArray completedPieces; QBitArray incompletePieces; int pieceCount; <span class="comment">// Progress</span> 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)) { <span class="comment">// Connect the file manager</span> 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())); <span class="comment">// Connect the tracker client</span> 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; } <span class="comment">// Calculate SHA1 hash of the "info" section in the torrent</span> QByteArray infoValue = d->metaInfo.infoValue(); SHA1Context sha; SHA1Reset(&sha); SHA1Input(&sha, (const unsigned char *)infoValue.constData(), infoValue.size()); SHA1Result(&sha); unsigned char *digest = (unsigned char *)sha.Message_Digest; d->infoHash.resize(20); for (int i = 0; i < 5; ++i) { #if Q_BYTE_ORDER == Q_BIG_ENDIAN d->infoHash[i * 4 + 3] = digest[i * 4 + 3]; d->infoHash[i * 4 + 2] = digest[i * 4 + 2]; d->infoHash[i * 4 + 1] = digest[i * 4 + 1]; d->infoHash[i * 4 + 0] = digest[i * 4 + 0]; #else d->infoHash[i * 4 + 0] = digest[i * 4 + 3]; d->infoHash[i * 4 + 1] = digest[i * 4 + 2]; d->infoHash[i * 4 + 2] = digest[i * 4 + 1]; d->infoHash[i * 4 + 3] = digest[i * 4 + 0]; #endif } 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) { <span class="comment">// Recover partially completed pieces</span> 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; <span class="comment">// Save the state of all partially downloaded pieces into a format</span> <span class="comment">// suitable for storing in settings.</span> 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); <span class="comment">// Initialize the file manager</span> 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); <span class="comment">// Update the state</span> State oldState = d->state; d->setState(Stopping); <span class="comment">// Stop the timer</span> if (d->transferRateTimer) { killTimer(d->transferRateTimer); d->transferRateTimer = 0; } <span class="comment">// Abort all existing connections</span> foreach (PeerWireClient *client, d->connections) client->abort(); d->connections.clear(); <span class="comment">// Perhaps stop the tracker</span> if (oldState > Preparing) { d->trackerClient.stop(); } else { d->setState(Idle); emit stopped(); } } void TorrentClient::setPaused(bool paused) { if (paused) { <span class="comment">// Abort all connections, and set the max number of</span> <span class="comment">// connections to 0. Keep the list of peers, so we can quickly</span> <span class="comment">// resume later.</span> d->setState(Paused); foreach (PeerWireClient *client, d->connections) client->abort(); d->connections.clear(); TorrentServer::instance()->removeClient(this); } else { <span class="comment">// Restore the max number of connections, and start the peer</span> <span class="comment">// connector. We should also quickly start receiving incoming</span> <span class="comment">// connections.</span> 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) { <span class="comment">// Update the state of who's choked and who's not</span> scheduleUploads(); return; } if (event->timerId() != d->transferRateTimer) { QObject::timerEvent(event); return; } <span class="comment">// Calculate average upload/download rate</span> 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)); <span class="comment">// Stop the timer if there is no activity.</span> if (downloadBytesPerSecond == 0 && uploadBytesPerSecond == 0) { killTimer(d->transferRateTimer); d->transferRateTimer = 0; } } void TorrentClient::sendToPeer(int readId, int pieceIndex, int begin, const QByteArray &data) { <span class="comment">// Send the requested block to the peer if the client connection</span> <span class="comment">// still exists; otherwise do nothing. This slot is called by the</span> <span class="comment">// file manager after it has read a block of data.</span> 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() { <span class="comment">// Update our list of completed and incomplete pieces.</span> 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(); <span class="comment">// If the checksums show that what the dumped state thought was</span> <span class="comment">// partial was in fact complete, then we trust the checksums.</span> 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); <span class="comment">// Start the server</span> TorrentServer *server = TorrentServer::instance(); if (!server->isListening()) { <span class="comment">// Set up the peer wire server</span> 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); <span class="comment">// Start the tracker client</span> d->trackerClient.setUploadCount(d->uploadedBytes); d->trackerClient.setDownloadCount(d->downloadedBytes); d->trackerClient.start(d->metaInfo); } void TorrentClient::pieceVerified(int pieceIndex, bool ok) { TorrentPiece *piece = d->pendingPieces.value(pieceIndex); <span class="comment">// Remove this piece from all payloads</span> 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) { <span class="comment">// If a piece did not pass the SHA1 check, we'll simply clear</span> <span class="comment">// its state, and the scheduler will re-request it</span> piece->inProgress = false; piece->completedBlocks.fill(false); piece->requestedBlocks.fill(false); d->callScheduler(); return; } <span class="comment">// Update the peer list so we know who's still interesting.</span> 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; } <span class="comment">// Delete the piece and update our structures.</span> delete piece; d->pendingPieces.remove(pieceIndex); d->completedPieces.setBit(pieceIndex); d->incompletePieces.clearBit(pieceIndex); <span class="comment">// Notify connected peers.</span> foreach (PeerWireClient *client, d->connections) { if (client->state() == QAbstractSocket::ConnectedState && !client->availablePieces().testBit(pieceIndex)) { client->sendPieceNotification(pieceIndex); } } <span class="comment">// Notify the tracker if we've entered Seeding status; otherwise</span> <span class="comment">// call the scheduler.</span> int completed = d->completedPieces.count(true); if (completed == d->pieceCount) { if (d->state != Seeding) { d->setState(Seeding); d->trackerClient.start(d->metaInfo); } } 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); <span class="comment">// Find the list of peers we are not currently connected to, where</span> <span class="comment">// the more interesting peers are listed more than once.</span> QList<TorrentPeer *> weighedPeers = weighedFreePeers(); <span class="comment">// Start as many connections as we can</span> 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; <span class="comment">// Pick a random peer from the list of weighed peers.</span> TorrentPeer *peer = weighedPeers.takeAt(qrand() % weighedPeers.size()); weighedPeers.removeAll(peer); peer->connectStart = QDateTime::currentDateTime().toTime_t(); peer->lastVisited = peer->connectStart; <span class="comment">// Connect to the peer.</span> client->setPeer(peer); client->connectToHost(peer->address, peer->port); } } QList<TorrentPeer *> TorrentClient::weighedFreePeers() const { QList<TorrentPeer *> weighedPeers; <span class="comment">// Generate a list of peers that we want to connect to.</span> 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; } <span class="comment">// Nothing to connect to</span> if (freePeers.isEmpty()) return weighedPeers; <span class="comment">// Assign points based on connection speed and pieces available.</span> 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) <span class="comment">// An unknown peer is as interesting as a seed</span> tmp += d->pieceCount; <span class="comment">// 1/5 of the total score for each second below 5 it takes to</span> <span class="comment">// connect.</span> if (peer->connectTime < 5) tmp += (d->pieceCount / 10) * (5 - peer->connectTime); } points << QPair<int, TorrentPeer *>(tmp, peer); } qSort(points); <span class="comment">// Minimize the list so the point difference is never more than 1.</span> 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); } <span class="comment">// Now make up a list of peers where the ones with more points are</span> <span class="comment">// listed many times.</span> 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) { <span class="comment">// Connect signals</span> initializeConnection(client); <span class="comment">// Initialize this client</span> 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()); <span class="comment">// Update connection statistics.</span> foreach (TorrentPeer *peer, d->peers) { if (peer->port == client->peerPort() && peer->address == client->peerAddress()) { peer->connectTime = peer->lastVisited - peer->connectStart; break; } } <span class="comment">// Send handshake and piece list</span> 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()); <span class="comment">// Remove the host from our list of known peers if the connection</span> <span class="comment">// failed.</span> if (client->peer() && client->error() == QAbstractSocket::ConnectionRefusedError) d->peers.removeAll(client->peer()); <span class="comment">// Remove the client from RateController and all structures.</span> 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); } <span class="comment">// Remove pending read requests.</span> QMapIterator<int, PeerWireClient *> it2(d->readIds); while (it2.findNext(client)) d->readIds.remove(it2.key()); <span class="comment">// Delete the client later.</span> 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()); <span class="comment">// Find the peer in our list of announced peers. If it's there,</span> <span class="comment">// then we can use the piece list into to gather statistics that</span> <span class="comment">// help us decide what peers to connect to.</span> 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; } <span class="comment">// If the peer is a seed, and we are in seeding mode, then the</span> <span class="comment">// peer is uninteresting.</span> 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; } } <span class="comment">// Update our list of available pieces.</span> if (peer) { peer->pieces = pieces; peer->numCompletedPieces = pieces.count(true); } <span class="comment">// Check for interesting pieces, and tell the peer whether we are</span> <span class="comment">// interested or not.</span> 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()); <span class="comment">// Silently ignore requests from choked peers</span> if (client->peerWireState() & PeerWireClient::ChokingPeer) return; <span class="comment">// Silently ignore requests for pieces we don't have.</span> if (!d->completedPieces.testBit(pieceIndex)) return; <span class="comment">// Request the block from the file manager</span> 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; } <span class="comment">// Ignore it if we already have this block.</span> int blockBit = begin / BlockSize; TorrentPiece *piece = d->pendingPieces.value(pieceIndex); if (!piece || piece->completedBlocks.testBit(blockBit)) { <span class="comment">// Discard blocks that we already have, and fill up the pipeline.</span> requestMore(client); return; } <span class="comment">// If we are in warmup or endgame mode, cancel all duplicate</span> <span class="comment">// requests for this block.</span> 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); <span class="comment">// Store this block</span> d->fileManager.write(pieceIndex, begin, data); piece->completedBlocks.setBit(blockBit); piece->requestedBlocks.clearBit(blockBit); if (blocksLeftForPiece(piece) == 0) { <span class="comment">// Ask the file manager to verify the newly downloaded piece</span> d->fileManager.verifyPiece(piece->index); <span class="comment">// Remove this piece from all payloads</span> 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; } } <span class="comment">// Fill up the pipeline.</span> 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() { <span class="comment">// Generate a list of clients sorted by their transfer</span> <span class="comment">// speeds. When leeching, we sort by download speed, and when</span> <span class="comment">// seeding, we sort by upload speed. Seeds are left out; there's</span> <span class="comment">// no use in unchoking them.</span> 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); } } } <span class="comment">// Unchoke the top 'MaxUploads' downloaders (peers that we are</span> <span class="comment">// uploading to) and choke all others.</span> 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); } <span class="comment">// Only interested peers are left in allClients. Unchoke one</span> <span class="comment">// random peer to allow it to compete for a position among the</span> <span class="comment">// downloaders. (This is known as an "optimistic unchoke".)</span> 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; <span class="comment">// Check what each client is doing, and assign payloads to those</span> <span class="comment">// who are either idle or done.</span> foreach (PeerWireClient *client, d->connections) schedulePieceForClient(client); } void TorrentClient::schedulePieceForClient(PeerWireClient *client) { <span class="comment">// Only schedule connected clients.</span> if (client->state() != QTcpSocket::ConnectedState) return; <span class="comment">// The peer has choked us; try again later.</span> if (client->peerWireState() & PeerWireClient::ChokedByPeer) return; <span class="comment">// Make a list of all the client's pending pieces, and count how</span> <span class="comment">// many blocks have been requested.</span> 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; } <span class="comment">// Skip clients that already have too many blocks in progress.</span> if (client->incomingBlocks().size() >= ((d->state == Endgame || d->state == WarmingUp) ? MaxBlocksInMultiMode : MaxBlocksInProgress)) return; <span class="comment">// If all pieces are in progress, but we haven't filled up our</span> <span class="comment">// block requesting quota, then we need to schedule another piece.</span> if (!somePiecesAreNotInProgress || client->incomingBlocks().size() > 0) lastPendingPiece = 0; TorrentPiece *piece = lastPendingPiece; <span class="comment">// In warmup state, all clients request blocks from the same pieces.</span> 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; } <span class="comment">// If no pieces are currently in progress, schedule a new one.</span> if (!piece) { <span class="comment">// Build up a list of what pieces that we have not completed</span> <span class="comment">// are available to this client.</span> QBitArray incompletePiecesAvailableToClient = d->incompletePieces; <span class="comment">// Remove all pieces that are marked as being in progress</span> <span class="comment">// already (i.e., pieces that this or other clients are</span> <span class="comment">// already waiting for). A special rule applies to warmup and</span> <span class="comment">// endgame mode; there, we allow several clients to request</span> <span class="comment">// the same piece. In endgame mode, this only applies to</span> <span class="comment">// clients that are currently uploading (more than 1.0KB/s).</span> 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; } } <span class="comment">// Remove all pieces that the client cannot download.</span> incompletePiecesAvailableToClient &= client->availablePieces(); <span class="comment">// Remove all pieces that this client has already requested.</span> foreach (int i, currentPieces) incompletePiecesAvailableToClient.clearBit(i); <span class="comment">// Only continue if more pieces can be scheduled. If no pieces</span> <span class="comment">// are available and no blocks are in progress, just leave</span> <span class="comment">// the connection idle; it might become interesting later.</span> if (incompletePiecesAvailableToClient.count(true) == 0) return; <span class="comment">// Check if any of the partially completed pieces can be</span> <span class="comment">// recovered, and if so, pick a random one of them.</span> 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) { <span class="comment">// Pick a random piece 3 out of 4 times; otherwise, pick either</span> <span class="comment">// one of the most common or the least common pieces available,</span> <span class="comment">// depending on the state we're in.</span> int pieceIndex = 0; if (d->state == WarmingUp || (qrand() & 4) == 0) { int *occurrances = new int[d->pieceCount]; memset(occurrances, 0, d->pieceCount * sizeof(int)); <span class="comment">// Count how many of each piece are available.</span> 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]; } } <span class="comment">// Find the rarest or most common pieces.</span> int numOccurrances = d->state == WarmingUp ? 0 : 99999; QList<int> piecesReadyForDownload; for (int i = 0; i < d->pieceCount; ++i) { if (d->state == WarmingUp) { <span class="comment">// Add common pieces</span> if (occurrances[i] >= numOccurrances && incompletePiecesAvailableToClient.testBit(i)) { if (occurrances[i] > numOccurrances) piecesReadyForDownload.clear(); piecesReadyForDownload.append(i); numOccurrances = occurrances[i]; } } else { <span class="comment">// Add rare pieces</span> if (occurrances[i] <= numOccurrances && incompletePiecesAvailableToClient.testBit(i)) { if (occurrances[i] < numOccurrances) piecesReadyForDownload.clear(); piecesReadyForDownload.append(i); numOccurrances = occurrances[i]; } } } <span class="comment">// Select one piece randomly</span> pieceIndex = piecesReadyForDownload.at(qrand() % piecesReadyForDownload.size()); delete [] occurrances; } else { <span class="comment">// Make up a list of available piece indices, and pick</span> <span class="comment">// a random one.</span> 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()); } <span class="comment">// Create a new TorrentPiece and fill in all initial</span> <span class="comment">// properties.</span> 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); } <span class="comment">// Request more blocks from all pending pieces.</span> requestMore(client); } void TorrentClient::requestMore(PeerWireClient *client) { <span class="comment">// Make a list of all pieces this client is currently waiting for,</span> <span class="comment">// and count the number of blocks in progress.</span> 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; } <span class="comment">// If no pieces are in progress, call the scheduler.</span> if (piecesInProgress.isEmpty() && d->incompletePieces.count(true)) { d->callScheduler(); return; } <span class="comment">// If too many pieces are in progress, there's nothing to do.</span> int maxInProgress = ((d->state == Endgame || d->state == WarmingUp) ? MaxBlocksInMultiMode : MaxBlocksInProgress); if (numBlocksInProgress == maxInProgress) return; <span class="comment">// Starting with the first piece that we're waiting for, request</span> <span class="comment">// blocks until the quota is filled up.</span> foreach (TorrentPiece *piece, piecesInProgress) { numBlocksInProgress += requestBlocks(client, piece, maxInProgress - numBlocksInProgress); if (numBlocksInProgress == maxInProgress) break; } <span class="comment">// If we still didn't fill up the quota, we need to schedule more</span> <span class="comment">// pieces.</span> if (numBlocksInProgress < maxInProgress && d->state != WarmingUp) d->callScheduler(); } int TorrentClient::requestBlocks(PeerWireClient *client, TorrentPiece *piece, int maxBlocks) { <span class="comment">// Generate the list of incomplete blocks for this piece.</span> 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; } <span class="comment">// Nothing more to request.</span> 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) { <span class="comment">// By randomizing the list of blocks to request, we</span> <span class="comment">// significantly speed up the warmup and endgame modes, where</span> <span class="comment">// the same blocks are requested from multiple peers. The</span> <span class="comment">// speedup comes from an increased chance of receiving</span> <span class="comment">// different blocks from the different peers.</span> 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; } } <span class="comment">// Request no more blocks than we've been asked to.</span> int blocksToRequest = qMin(maxBlocks, bits.size()); <span class="comment">// Calculate the offset and size of each block, and send requests.</span> 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; <span class="comment">// When the peer chokes us, we immediately forget about all blocks</span> <span class="comment">// we've requested from it. We also remove the piece from out</span> <span class="comment">// payload, making it available to other clients.</span> 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; <span class="comment">// We got unchoked, which means we can request more blocks.</span> if (d->state != Seeding) d->callScheduler(); } void TorrentClient::addToPeerList(const QList<TorrentPeer> &peerList) { <span class="comment">// Add peers we don't already know of to our list of peers.</span> foreach (TorrentPeer peer, peerList) { if (peer.address == TorrentServer::instance()->serverAddress() && peer.port == TorrentServer::instance()->serverPort()) 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; } } <span class="comment">// If we've got more peers than we can connect to, we remove some</span> <span class="comment">// of the peers that have no (or low) activity.</span> int maxPeers = ConnectionManager::instance()->maxConnections() * 3; if (d->peers.size() > maxPeers) { <span class="comment">// Find what peers are currently connected & active</span> QSet<TorrentPeer *> activePeers; foreach (TorrentPeer *peer, d->peers) { foreach (PeerWireClient *client, d->connections) { if (client->peer() == peer && (client->downloadSpeed() + client->uploadSpeed()) > 1024) activePeers << peer; } } <span class="comment">// Remove inactive peers from the peer list until we're below</span> <span class="comment">// the max connections count.</span> 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()); <span class="comment">// If we still have too many peers, remove the oldest ones.</span> 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); } }</pre> <p /><address><hr /><div align="center"> <table width="100%" cellspacing="0" border="0"><tr class="address"> <td width="30%">Copyright © 2006 <a href="trolltech.html">Trolltech</a></td> <td width="40%" align="center"><a href="trademarks.html">Trademarks</a></td> <td width="30%" align="right"><div align="right">Qt 4.2.1</div></td> </tr></table></div></address></body> </html>