📄 downloadmanager.cpp
字号:
/*
* Copyright (C) 2001-2005 Jacek Sieka, arnetheduck on gmail point 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "stdinc.h"
#include "DCPlusPlus.h"
#include "DownloadManager.h"
#include "ResourceManager.h"
#include "ConnectionManager.h"
#include "QueueManager.h"
#include "CryptoManager.h"
#include "HashManager.h"
#include "LogManager.h"
#include "SFVReader.h"
#include "User.h"
#include "File.h"
#include "FilteredFile.h"
#include "MerkleCheckOutputStream.h"
#include <limits>
// some strange mac definition
#ifdef ff
#undef ff
#endif
static const string DOWNLOAD_AREA = "Downloads";
const string Download::ANTI_FRAG_EXT = ".antifrag";
Download::Download() throw() : file(NULL),
crcCalc(NULL), tth(NULL), treeValid(false) {
}
Download::Download(QueueItem* qi) throw() : source(qi->getCurrent()->getPath()),
target(qi->getTarget()), tempTarget(qi->getTempTarget()), file(NULL),
crcCalc(NULL), tth(qi->getTTH()), treeValid(false) {
setSize(qi->getSize());
if(qi->isSet(QueueItem::FLAG_USER_LIST))
setFlag(Download::FLAG_USER_LIST);
if(qi->isSet(QueueItem::FLAG_RESUME))
setFlag(Download::FLAG_RESUME);
if(qi->getCurrent()->isSet(QueueItem::Source::FLAG_UTF8))
setFlag(Download::FLAG_UTF8);
}
AdcCommand Download::getCommand(bool zlib, bool tthf) {
AdcCommand cmd(AdcCommand::CMD_GET);
if(isSet(FLAG_TREE_DOWNLOAD)) {
cmd.addParam("tthl");
} else if(isSet(FLAG_PARTIAL_LIST)) {
cmd.addParam("list");
} else {
cmd.addParam("file");
}
if(tthf && getTTH() != NULL) {
cmd.addParam("TTH/" + getTTH()->toBase32());
} else {
cmd.addParam(Util::toAdcFile(getSource()));
}
cmd.addParam(Util::toString(getPos()));
cmd.addParam(Util::toString(getSize() - getPos()));
if(zlib && getSize() != -1 && BOOLSETTING(COMPRESS_TRANSFERS)) {
cmd.addParam("ZL1");
}
return cmd;
}
void DownloadManager::on(TimerManagerListener::Second, u_int32_t /*aTick*/) throw() {
Lock l(cs);
Download::List tickList;
// Tick each ongoing download
for(Download::Iter i = downloads.begin(); i != downloads.end(); ++i) {
if((*i)->getTotal() > 0) {
tickList.push_back(*i);
}
}
if(tickList.size() > 0)
fire(DownloadManagerListener::Tick(), tickList);
}
void DownloadManager::FileMover::moveFile(const string& source, const string& target) {
Lock l(cs);
files.push_back(make_pair(source, target));
if(!active) {
active = true;
start();
}
}
int DownloadManager::FileMover::run() {
for(;;) {
FilePair next;
{
Lock l(cs);
if(files.empty()) {
active = false;
return 0;
}
next = files.back();
files.pop_back();
}
try {
File::renameFile(next.first, next.second);
} catch(const FileException&) {
// Too bad...
}
}
}
void DownloadManager::removeConnection(UserConnection::Ptr aConn, bool reuse /* = false */, bool ntd /* = false */) {
dcassert(aConn->getDownload() == NULL);
aConn->removeListener(this);
ConnectionManager::getInstance()->putDownloadConnection(aConn, reuse, ntd);
}
class TreeOutputStream : public OutputStream {
public:
TreeOutputStream(TigerTree& aTree) : tree(aTree), bufPos(0) {
}
virtual size_t write(const void* xbuf, size_t len) throw(Exception) {
size_t pos = 0;
u_int8_t* b = (u_int8_t*)xbuf;
while(pos < len) {
size_t left = len - pos;
if(bufPos == 0 && left >= TigerTree::HASH_SIZE) {
tree.getLeaves().push_back(TTHValue(b + pos));
pos += TigerTree::HASH_SIZE;
} else {
size_t bytes = min(TigerTree::HASH_SIZE - bufPos, left);
memcpy(buf + bufPos, b + pos, bytes);
bufPos += bytes;
pos += bytes;
if(bufPos == TigerTree::HASH_SIZE) {
tree.getLeaves().push_back(TTHValue(buf));
bufPos = 0;
}
}
}
return len;
}
virtual size_t flush() throw(Exception) {
return 0;
}
private:
TigerTree& tree;
u_int8_t buf[TigerTree::HASH_SIZE];
size_t bufPos;
};
void DownloadManager::checkDownloads(UserConnection* aConn) {
dcassert(aConn->getDownload() == NULL);
bool slotsFull = (SETTING(DOWNLOAD_SLOTS) != 0) && (getDownloadCount() >= (size_t)SETTING(DOWNLOAD_SLOTS));
bool speedFull = (SETTING(MAX_DOWNLOAD_SPEED) != 0) && (getAverageSpeed() >= (SETTING(MAX_DOWNLOAD_SPEED)*1024));
if( slotsFull || speedFull ) {
bool extraFull = (SETTING(DOWNLOAD_SLOTS) != 0) && (getDownloadCount() >= (size_t)(SETTING(DOWNLOAD_SLOTS)+3));
if(extraFull || !QueueManager::getInstance()->hasDownload(aConn->getUser(), QueueItem::HIGHEST)) {
removeConnection(aConn);
return;
}
}
Download* d = QueueManager::getInstance()->getDownload(aConn->getUser(), aConn->isSet(UserConnection::FLAG_SUPPORTS_TTHL));
if(d == NULL) {
removeConnection(aConn, true);
return;
}
{
Lock l(cs);
downloads.push_back(d);
}
d->setUserConnection(aConn);
aConn->setDownload(d);
aConn->setState(UserConnection::STATE_FILELENGTH);
if(d->isSet(Download::FLAG_RESUME)) {
dcassert(d->getSize() != -1);
const string& target = (d->getTempTarget().empty() ? d->getTarget() : d->getTempTarget());
int64_t start = File::getSize(target);
// Only use antifrag if we don't have a previous non-antifrag part
if( BOOLSETTING(ANTI_FRAG) && (start == -1) && (d->getSize() != -1) ) {
int64_t aSize = File::getSize(target + Download::ANTI_FRAG_EXT);
if(aSize == d->getSize())
start = d->getPos();
else
start = 0;
d->setFlag(Download::FLAG_ANTI_FRAG);
}
if(BOOLSETTING(ADVANCED_RESUME) && d->getTreeValid() && start > 0) {
d->setStartPos(getResumePos(d->getDownloadTarget(), d->getTigerTree(), start));
} else {
int rollback = SETTING(ROLLBACK);
if(rollback > start) {
d->setStartPos(0);
} else {
d->setStartPos(start - rollback);
d->setFlag(Download::FLAG_ROLLBACK);
}
}
} else {
d->setStartPos(0);
}
if(d->isSet(Download::FLAG_USER_LIST)) {
if(!aConn->isSet(UserConnection::FLAG_NMDC) || aConn->isSet(UserConnection::FLAG_SUPPORTS_XML_BZLIST)) {
d->setSource("files.xml.bz2");
d->setFlag(Download::FLAG_UTF8);
}
}
// File ok for adcget in nmdc-conns
bool adcOk = d->isSet(Download::FLAG_UTF8) || (aConn->isSet(UserConnection::FLAG_SUPPORTS_TTHF) && d->getTTH() != NULL);
if(!aConn->isSet(UserConnection::FLAG_NMDC) || (aConn->isSet(UserConnection::FLAG_SUPPORTS_ADCGET) && adcOk)) {
aConn->send(d->getCommand(
aConn->isSet(UserConnection::FLAG_SUPPORTS_ZLIB_GET),
aConn->isSet(!aConn->isSet(UserConnection::FLAG_NMDC) || UserConnection::FLAG_SUPPORTS_TTHF)
));
} else {
if(BOOLSETTING(COMPRESS_TRANSFERS) && aConn->isSet(UserConnection::FLAG_SUPPORTS_GETZBLOCK) && d->getSize() != -1 ) {
// This one, we'll download with a zblock download instead...
d->setFlag(Download::FLAG_ZDOWNLOAD);
aConn->getZBlock(d->getSource(), d->getPos(), d->getBytesLeft(), d->isSet(Download::FLAG_UTF8));
} else if(aConn->isSet(UserConnection::FLAG_SUPPORTS_XML_BZLIST) && d->isSet(Download::FLAG_UTF8)) {
aConn->uGetBlock(d->getSource(), d->getPos(), d->getBytesLeft());
} else {
aConn->get(d->getSource(), d->getPos());
}
}
}
class DummyOutputStream : public OutputStream {
public:
virtual size_t write(const void*, size_t n) throw(Exception) { return n; }
virtual size_t flush() throw(Exception) { return 0; }
};
int64_t DownloadManager::getResumePos(const string& file, const TigerTree& tt, int64_t startPos) {
// Always discard data until the last block
startPos = startPos - (startPos % tt.getBlockSize());
if(startPos < tt.getBlockSize())
return 0;
DummyOutputStream dummy;
vector<u_int8_t> buf((size_t)min((int64_t)1024*1024, tt.getBlockSize()));
do {
int64_t blockPos = startPos - tt.getBlockSize();
MerkleCheckOutputStream<TigerTree, false> check(tt, &dummy, blockPos);
try {
File inFile(file, File::READ, File::OPEN);
inFile.setPos(blockPos);
int64_t bytesLeft = tt.getBlockSize();
while(bytesLeft > 0) {
size_t n = (size_t)min((int64_t)buf.size(), bytesLeft);
size_t nr = inFile.read(&buf[0], n);
check.write(&buf[0], nr);
bytesLeft -= nr;
if(bytesLeft > 0 && nr == 0) {
// Huh??
throw Exception();
}
}
check.flush();
break;
} catch(const Exception&) {
dcdebug("Removed bad block at " I64_FMT "\n", blockPos);
}
startPos = blockPos;
} while(startPos > 0);
return startPos;
}
void DownloadManager::on(UserConnectionListener::Sending, UserConnection* aSource, int64_t aBytes) throw() {
if(aSource->getState() != UserConnection::STATE_FILELENGTH) {
dcdebug("DM::onFileLength Bad state, ignoring\n");
return;
}
if(prepareFile(aSource, (aBytes == -1) ? -1 : aSource->getDownload()->getPos() + aBytes, aSource->getDownload()->isSet(Download::FLAG_ZDOWNLOAD))) {
aSource->setDataMode();
}
}
void DownloadManager::on(UserConnectionListener::FileLength, UserConnection* aSource, int64_t aFileLength) throw() {
if(aSource->getState() != UserConnection::STATE_FILELENGTH) {
dcdebug("DM::onFileLength Bad state, ignoring\n");
return;
}
if(prepareFile(aSource, aFileLength, aSource->getDownload()->isSet(Download::FLAG_ZDOWNLOAD))) {
aSource->setDataMode();
aSource->startSend();
}
}
void DownloadManager::on(AdcCommand::SND, UserConnection* aSource, const AdcCommand& cmd) throw() {
if(aSource->getState() != UserConnection::STATE_FILELENGTH) {
dcdebug("DM::onFileLength Bad state, ignoring\n");
return;
}
const string& type = cmd.getParam(0);
int64_t bytes = Util::toInt64(cmd.getParam(3));
if(!(type == "file" || (type == "tthl" && aSource->getDownload()->isSet(Download::FLAG_TREE_DOWNLOAD)) ||
(type == "list" && aSource->getDownload()->isSet(Download::FLAG_PARTIAL_LIST))) )
{
// Uhh??? We didn't ask for this?
aSource->disconnect();
return;
}
if(prepareFile(aSource, (bytes == -1) ? -1 : aSource->getDownload()->getPos() + bytes, cmd.hasFlag("ZL", 4))) {
aSource->setDataMode();
}
}
class RollbackException : public FileException {
public:
RollbackException (const string& aError) : FileException(aError) { };
};
template<bool managed>
class RollbackOutputStream : public OutputStream {
public:
RollbackOutputStream(File* f, OutputStream* aStream, size_t bytes) : s(aStream), pos(0), bufSize(bytes), buf(new u_int8_t[bytes]) {
size_t n = bytes;
f->read(buf, n);
f->movePos(-((int64_t)bytes));
}
virtual ~RollbackOutputStream() throw() { delete[] buf; if(managed) delete s; };
virtual size_t flush() throw(FileException) {
return s->flush();
}
virtual size_t write(const void* b, size_t len) throw(FileException) {
if(buf != NULL) {
size_t n = min(len, bufSize - pos);
u_int8_t* wb = (u_int8_t*)b;
if(memcmp(buf + pos, wb, n) != 0) {
throw RollbackException(STRING(ROLLBACK_INCONSISTENCY));
}
pos += n;
if(pos == bufSize) {
delete buf;
buf = NULL;
}
}
return s->write(b, len);
}
private:
OutputStream* s;
size_t pos;
size_t bufSize;
u_int8_t* buf;
};
bool DownloadManager::prepareFile(UserConnection* aSource, int64_t newSize, bool z) {
Download* d = aSource->getDownload();
dcassert(d != NULL);
if(newSize != -1) {
d->setSize(newSize);
}
if(d->getPos() >= d->getSize()) {
// Already finished?
aSource->setDownload(NULL);
removeDownload(d);
QueueManager::getInstance()->putDownload(d, true);
removeConnection(aSource);
return false;
}
dcassert(d->getSize() != -1);
if(d->isSet(Download::FLAG_PARTIAL_LIST)) {
d->setFile(new StringOutputStream(d->getPFS()));
} else if(d->isSet(Download::FLAG_TREE_DOWNLOAD)) {
d->setFile(new TreeOutputStream(d->getTigerTree()));
} else {
string target = d->getDownloadTarget();
File::ensureDirectory(target);
if(d->isSet(Download::FLAG_USER_LIST)) {
if(!aSource->isSet(UserConnection::FLAG_NMDC) || aSource->isSet(UserConnection::FLAG_SUPPORTS_XML_BZLIST)) {
target += ".xml.bz2";
} else {
target += ".DcLst";
}
}
File* file = NULL;
try {
// Let's check if we can find this file in a any .SFV...
int trunc = d->isSet(Download::FLAG_RESUME) ? 0 : File::TRUNCATE;
file = new File(target, File::RW, File::OPEN | File::CREATE | trunc);
if(d->isSet(Download::FLAG_ANTI_FRAG)) {
file->setSize(d->getSize());
}
file->setPos(d->getPos());
} catch(const FileException& e) {
delete file;
removeDownload(d);
fire(DownloadManagerListener::Failed(), d, STRING(COULD_NOT_OPEN_TARGET_FILE) + e.getError());
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -