summaryrefslogtreecommitdiffstats
path: root/libktorrent/torrent/chunkdownload.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libktorrent/torrent/chunkdownload.cpp')
-rw-r--r--libktorrent/torrent/chunkdownload.cpp484
1 files changed, 484 insertions, 0 deletions
diff --git a/libktorrent/torrent/chunkdownload.cpp b/libktorrent/torrent/chunkdownload.cpp
new file mode 100644
index 0000000..51e9db9
--- /dev/null
+++ b/libktorrent/torrent/chunkdownload.cpp
@@ -0,0 +1,484 @@
+/***************************************************************************
+ * Copyright (C) 2005 by Joris Guisson *
+ * joris.guisson@gmail.com *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program; if not, write to the *
+ * Free Software Foundation, Inc., *
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
+ ***************************************************************************/
+
+#include <algorithm>
+#include <util/file.h>
+#include <util/log.h>
+#include <util/array.h>
+#include "chunkdownload.h"
+#include "downloader.h"
+#include "chunk.h"
+#include "peer.h"
+#include "peermanager.h"
+#include "piece.h"
+#include "peerdownloader.h"
+
+#include <klocale.h>
+
+namespace bt
+{
+
+ class DownloadStatus : public std::set<Uint32>
+ {
+ public:
+ // typedef std::set<Uint32>::iterator iterator;
+
+ DownloadStatus()
+ {
+
+ }
+
+ ~DownloadStatus()
+ {
+ }
+
+ void add(Uint32 p)
+ {
+ insert(p);
+ }
+
+ void remove(Uint32 p)
+ {
+ erase(p);
+ }
+
+ bool contains(Uint32 p)
+ {
+ return count(p) > 0;
+ }
+ };
+
+ ChunkDownload::ChunkDownload(Chunk* chunk) : chunk(chunk)
+ {
+ num = num_downloaded = 0;
+
+ num = chunk->getSize() / MAX_PIECE_LEN;
+
+ if (chunk->getSize() % MAX_PIECE_LEN != 0)
+ {
+ last_size = chunk->getSize() % MAX_PIECE_LEN;
+ num++;
+ }
+ else
+ {
+ last_size = MAX_PIECE_LEN;
+ }
+
+ pieces = BitSet(num);
+ pieces.clear();
+
+ for (Uint32 i = 0;i < num;i++)
+ piece_queue.append(i);
+
+ dstatus.setAutoDelete(true);
+ chunk->ref();
+
+ num_pieces_in_hash = 0;
+ if (usingContinuousHashing())
+ hash_gen.start();
+
+ }
+
+ ChunkDownload::~ChunkDownload()
+ {
+ chunk->unref();
+ }
+
+ bool ChunkDownload::piece(const Piece & p,bool & ok)
+ {
+ ok = false;
+ timer.update();
+
+ Uint32 pp = p.getOffset() / MAX_PIECE_LEN;
+ if (pieces.get(pp))
+ return false;
+
+
+ DownloadStatus* ds = dstatus.find(p.getPeer());
+ if (ds)
+ ds->remove(pp);
+
+ Uint8* buf = chunk->getData();
+ if (buf)
+ {
+ ok = true;
+ memcpy(buf + p.getOffset(),p.getData(),p.getLength());
+ pieces.set(pp,true);
+ piece_queue.remove(pp);
+ piece_providers.insert(p.getPeer());
+ num_downloaded++;
+ if (pdown.count() > 1)
+ {
+ endgameCancel(p);
+ }
+
+ if (usingContinuousHashing())
+ updateHash();
+
+ if (num_downloaded >= num)
+ {
+ // finalize hash
+ if (usingContinuousHashing())
+ hash_gen.end();
+
+ releaseAllPDs();
+ return true;
+ }
+ }
+
+ for (QPtrList<PeerDownloader>::iterator i = pdown.begin();i != pdown.end();++i)
+ sendRequests(*i);
+
+ return false;
+ }
+
+ void ChunkDownload::releaseAllPDs()
+ {
+ for (Uint32 i = 0;i < pdown.count();i++)
+ {
+ PeerDownloader* pd = pdown.at(i);
+ pd->release();
+ disconnect(pd,SIGNAL(timedout(const Request& )),this,SLOT(onTimeout(const Request& )));
+ disconnect(pd,SIGNAL(rejected( const Request& )),this,SLOT(onRejected( const Request& )));
+ }
+ dstatus.clear();
+ pdown.clear();
+ }
+
+ bool ChunkDownload::assignPeer(PeerDownloader* pd)
+ {
+ if (!pd || pdown.contains(pd))
+ return false;
+
+ pd->grab();
+ pdown.append(pd);
+ dstatus.insert(pd->getPeer()->getID(),new DownloadStatus());
+ sendRequests(pd);
+ connect(pd,SIGNAL(timedout(const Request& )),this,SLOT(onTimeout(const Request& )));
+ connect(pd,SIGNAL(rejected( const Request& )),this,SLOT(onRejected( const Request& )));
+ return true;
+ }
+
+ void ChunkDownload::notDownloaded(const Request & r,bool reject)
+ {
+ // find the peer
+ DownloadStatus* ds = dstatus.find(r.getPeer());
+ if (ds)
+ {
+ // Out() << "ds != 0" << endl;
+ Uint32 p = r.getOffset() / MAX_PIECE_LEN;
+ ds->remove(p);
+ }
+
+ // go over all PD's and do requets again
+ for (QPtrList<PeerDownloader>::iterator i = pdown.begin();i != pdown.end();++i)
+ sendRequests(*i);
+ }
+
+ void ChunkDownload::onRejected(const Request & r)
+ {
+ if (chunk->getIndex() == r.getIndex())
+ {
+// Out(SYS_CON|LOG_DEBUG) << QString("Request rejected %1 %2 %3 %4").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()).arg(r.getPeer()) << endl;
+
+ notDownloaded(r,true);
+ }
+ }
+
+ void ChunkDownload::onTimeout(const Request & r)
+ {
+ // see if we are dealing with a piece of ours
+ if (chunk->getIndex() == r.getIndex())
+ {
+ Out(SYS_CON|LOG_DEBUG) << QString("Request timed out %1 %2 %3 %4").arg(r.getIndex()).arg(r.getOffset()).arg(r.getLength()).arg(r.getPeer()) << endl;
+
+ notDownloaded(r,false);
+ }
+ }
+
+ void ChunkDownload::sendRequests(PeerDownloader* pd)
+ {
+ timer.update();
+ DownloadStatus* ds = dstatus.find(pd->getPeer()->getID());
+ if (!ds)
+ return;
+
+ // if the peer is choked and we are not downloading an allowed fast chunk
+ if (pd->isChoked())
+ return;
+
+ Uint32 num_visited = 0;
+ while (num_visited < piece_queue.count() && pd->canAddRequest())
+ {
+ // get the first one in the queue
+ Uint32 i = piece_queue.first();
+ if (!ds->contains(i))
+ {
+ // send request
+ pd->download(
+ Request(
+ chunk->getIndex(),
+ i*MAX_PIECE_LEN,
+ i+1<num ? MAX_PIECE_LEN : last_size,
+ pd->getPeer()->getID()));
+ ds->add(i);
+ }
+ // move to the back so that it will take a while before it's turn is up
+ piece_queue.pop_front();
+ piece_queue.append(i);
+ num_visited++;
+ }
+
+ if (piece_queue.count() < 2 && piece_queue.count() > 0)
+ pd->setNearlyDone(true);
+ }
+
+
+
+ void ChunkDownload::update()
+ {
+ // go over all PD's and do requets again
+ for (QPtrList<PeerDownloader>::iterator i = pdown.begin();i != pdown.end();++i)
+ sendRequests(*i);
+ }
+
+
+ void ChunkDownload::sendCancels(PeerDownloader* pd)
+ {
+ DownloadStatus* ds = dstatus.find(pd->getPeer()->getID());
+ if (!ds)
+ return;
+
+ DownloadStatus::iterator itr = ds->begin();
+ while (itr != ds->end())
+ {
+ Uint32 i = *itr;
+ pd->cancel(
+ Request(
+ chunk->getIndex(),
+ i*MAX_PIECE_LEN,
+ i+1<num ? MAX_PIECE_LEN : last_size,0));
+ itr++;
+ }
+ ds->clear();
+ timer.update();
+ }
+
+ void ChunkDownload::endgameCancel(const Piece & p)
+ {
+ QPtrList<PeerDownloader>::iterator i = pdown.begin();
+ while (i != pdown.end())
+ {
+ PeerDownloader* pd = *i;
+ DownloadStatus* ds = dstatus.find(pd->getPeer()->getID());
+ Uint32 pp = p.getOffset() / MAX_PIECE_LEN;
+ if (ds && ds->contains(pp))
+ {
+ pd->cancel(Request(p));
+ ds->remove(pp);
+ }
+ i++;
+ }
+ }
+
+ void ChunkDownload::peerKilled(PeerDownloader* pd)
+ {
+ if (!pdown.contains(pd))
+ return;
+
+ dstatus.erase(pd->getPeer()->getID());
+ pdown.remove(pd);
+ disconnect(pd,SIGNAL(timedout(const Request& )),this,SLOT(onTimeout(const Request& )));
+ disconnect(pd,SIGNAL(rejected( const Request& )),this,SLOT(onRejected( const Request& )));
+ }
+
+
+ const Peer* ChunkDownload::getCurrentPeer() const
+ {
+ if (pdown.count() == 0)
+ return 0;
+ else
+ return pdown.getFirst()->getPeer();
+ }
+
+ Uint32 ChunkDownload::getChunkIndex() const
+ {
+ return chunk->getIndex();
+ }
+
+ QString ChunkDownload::getCurrentPeerID() const
+ {
+ if (pdown.count() == 0)
+ {
+ return QString::null;
+ }
+ else if (pdown.count() == 1)
+ {
+ const Peer* p = pdown.getFirst()->getPeer();
+ return p->getPeerID().identifyClient();
+ }
+ else
+ {
+ return i18n("1 peer","%n peers",pdown.count());
+ }
+ }
+
+ Uint32 ChunkDownload::getDownloadSpeed() const
+ {
+ Uint32 r = 0;
+ QPtrList<PeerDownloader>::const_iterator i = pdown.begin();
+ while (i != pdown.end())
+ {
+ const PeerDownloader* pd = *i;
+ r += pd->getPeer()->getDownloadRate();
+ i++;
+ }
+ return r;
+ }
+
+
+
+ void ChunkDownload::save(File & file)
+ {
+ ChunkDownloadHeader hdr;
+ hdr.index = chunk->getIndex();
+ hdr.num_bits = pieces.getNumBits();
+ hdr.buffered = chunk->getStatus() == Chunk::BUFFERED ? 1 : 0;
+ // save the chunk header
+ file.write(&hdr,sizeof(ChunkDownloadHeader));
+ // save the bitset
+ file.write(pieces.getData(),pieces.getNumBytes());
+ if (hdr.buffered)
+ {
+ // if it's a buffered chunk, save the contents to
+ file.write(chunk->getData(),chunk->getSize());
+ chunk->clear();
+ chunk->setStatus(Chunk::ON_DISK);
+ }
+ }
+
+ bool ChunkDownload::load(File & file,ChunkDownloadHeader & hdr)
+ {
+ // read pieces
+ if (hdr.num_bits != num)
+ return false;
+
+ pieces = BitSet(hdr.num_bits);
+ Array<Uint8> data(pieces.getNumBytes());
+ file.read(data,pieces.getNumBytes());
+ pieces = BitSet(data,hdr.num_bits);
+ num_downloaded = pieces.numOnBits();
+ if (hdr.buffered)
+ {
+ // if it's a buffered chunk, load the data to
+ if (file.read(chunk->getData(),chunk->getSize()) != chunk->getSize())
+ return false;
+ }
+
+ for (Uint32 i = 0;i < pieces.getNumBits();i++)
+ if (pieces.get(i))
+ piece_queue.remove(i);
+
+ updateHash();
+ return true;
+ }
+
+ Uint32 ChunkDownload::bytesDownloaded() const
+ {
+ Uint32 num_bytes = 0;
+ for (Uint32 i = 0;i < num;i++)
+ {
+ if (pieces.get(i))
+ {
+ num_bytes += i == num-1 ? last_size : MAX_PIECE_LEN;
+ }
+ }
+ return num_bytes;
+ }
+
+ void ChunkDownload::cancelAll()
+ {
+ QPtrList<PeerDownloader>::iterator i = pdown.begin();
+ while (i != pdown.end())
+ {
+ sendCancels(*i);
+ i++;
+ }
+ }
+
+ bool ChunkDownload::getOnlyDownloader(Uint32 & pid)
+ {
+ if (piece_providers.size() == 1)
+ {
+ pid = *piece_providers.begin();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ void ChunkDownload::getStats(Stats & s)
+ {
+ s.chunk_index = chunk->getIndex();
+ s.current_peer_id = getCurrentPeerID();
+ s.download_speed = getDownloadSpeed();
+ s.num_downloaders = getNumDownloaders();
+ s.pieces_downloaded = num_downloaded;
+ s.total_pieces = num;
+ }
+
+ bool ChunkDownload::isChoked() const
+ {
+ QPtrList<PeerDownloader>::const_iterator i = pdown.begin();
+ while (i != pdown.end())
+ {
+ const PeerDownloader* pd = *i;
+ // if there is one which isn't choked
+ if (!pd->isChoked())
+ return false;
+ i++;
+ }
+ return true;
+ }
+
+ void ChunkDownload::updateHash()
+ {
+ // update the hash until where we can
+ Uint32 nn = num_pieces_in_hash;
+ while (pieces.get(nn) && nn < num)
+ nn++;
+
+ for (Uint32 i = num_pieces_in_hash;i < nn;i++)
+ {
+ const Uint8* data = chunk->getData() + i * MAX_PIECE_LEN;
+ hash_gen.update(data,i == num - 1 ? last_size : MAX_PIECE_LEN);
+ }
+ num_pieces_in_hash = nn;
+ }
+
+ bool ChunkDownload::usingContinuousHashing() const
+ {
+ // if the pieces are larger then 1 MB we will be using the continuous hashing feature
+ return pieces.getNumBits() > 64;
+ }
+}
+#include "chunkdownload.moc"