You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
485 lines
11 KiB
485 lines
11 KiB
/***************************************************************************
|
|
* 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"
|