mirror of https://github.com/aria2/aria2
aria2 now doesn't assume download's completed just because file size matched
The only exception is zero-length file. If server tells file is zero-length and --checksum option is given, aria2 now correctly checks its checksum. There is one known issue: If downloaded file is zero-length file and .aria2 file exists, it will not be deleted on successful verification, because .aria2 file is not loaded.pull/8/head
parent
b6f8a3dbb3
commit
1c292f469e
|
@ -69,9 +69,7 @@ void ChecksumCheckIntegrityEntry::initValidator()
|
||||||
void
|
void
|
||||||
ChecksumCheckIntegrityEntry::onDownloadFinished
|
ChecksumCheckIntegrityEntry::onDownloadFinished
|
||||||
(std::vector<Command*>& commands, DownloadEngine* e)
|
(std::vector<Command*>& commands, DownloadEngine* e)
|
||||||
{
|
{}
|
||||||
getRequestGroup()->getDownloadContext()->setChecksumVerified(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void
|
||||||
ChecksumCheckIntegrityEntry::onDownloadIncomplete
|
ChecksumCheckIntegrityEntry::onDownloadIncomplete
|
||||||
|
|
|
@ -74,6 +74,9 @@
|
||||||
#include "CheckIntegrityEntry.h"
|
#include "CheckIntegrityEntry.h"
|
||||||
#include "error_code.h"
|
#include "error_code.h"
|
||||||
#include "SocketRecvBuffer.h"
|
#include "SocketRecvBuffer.h"
|
||||||
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
|
# include "ChecksumCheckIntegrityEntry.h"
|
||||||
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
|
|
||||||
namespace aria2 {
|
namespace aria2 {
|
||||||
|
|
||||||
|
@ -398,21 +401,32 @@ bool FtpNegotiationCommand::onFileSizeDetermined(off_t totalLength)
|
||||||
|
|
||||||
if(getDownloadContext()->knowsTotalLength() &&
|
if(getDownloadContext()->knowsTotalLength() &&
|
||||||
getRequestGroup()->downloadFinishedByFileLength()) {
|
getRequestGroup()->downloadFinishedByFileLength()) {
|
||||||
// TODO If metalink file does not contain size and it contains
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
// hash and file is not zero length, but remote server says the
|
// TODO Known issue: if .aria2 file exists, it will not be
|
||||||
// file size is 0, no hash check is performed in the current
|
// deleted on successful verification, because .aria2 file is
|
||||||
// implementation. See also
|
// not loaded. See also
|
||||||
// HttpResponseCommand::handleOtherEncoding()
|
// HttpResponseCommand::handleOtherEncoding()
|
||||||
getRequestGroup()->initPieceStorage();
|
getRequestGroup()->initPieceStorage();
|
||||||
getPieceStorage()->markAllPiecesDone();
|
if(getDownloadContext()->isChecksumVerificationNeeded()) {
|
||||||
getDownloadContext()->setChecksumVerified(true);
|
A2_LOG_DEBUG("Zero length file exists. Verify checksum.");
|
||||||
sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED;
|
SharedHandle<ChecksumCheckIntegrityEntry> entry
|
||||||
A2_LOG_NOTICE
|
(new ChecksumCheckIntegrityEntry(getRequestGroup()));
|
||||||
(fmt(MSG_DOWNLOAD_ALREADY_COMPLETED,
|
entry->initValidator();
|
||||||
getRequestGroup()->getGID(),
|
getPieceStorage()->getDiskAdaptor()->openExistingFile();
|
||||||
getRequestGroup()->getFirstFilePath().c_str()));
|
getDownloadEngine()->getCheckIntegrityMan()->pushEntry(entry);
|
||||||
|
sequence_ = SEQ_EXIT;
|
||||||
|
} else
|
||||||
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
|
{
|
||||||
|
getPieceStorage()->markAllPiecesDone();
|
||||||
|
getDownloadContext()->setChecksumVerified(true);
|
||||||
|
sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED;
|
||||||
|
A2_LOG_NOTICE
|
||||||
|
(fmt(MSG_DOWNLOAD_ALREADY_COMPLETED,
|
||||||
|
getRequestGroup()->getGID(),
|
||||||
|
getRequestGroup()->getFirstFilePath().c_str()));
|
||||||
|
}
|
||||||
poolConnection();
|
poolConnection();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -421,8 +435,25 @@ bool FtpNegotiationCommand::onFileSizeDetermined(off_t totalLength)
|
||||||
getPieceStorage()->getDiskAdaptor()->initAndOpenFile();
|
getPieceStorage()->getDiskAdaptor()->initAndOpenFile();
|
||||||
|
|
||||||
if(getDownloadContext()->knowsTotalLength()) {
|
if(getDownloadContext()->knowsTotalLength()) {
|
||||||
sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED;
|
A2_LOG_DEBUG("File length becomes zero and it means download completed.");
|
||||||
getPieceStorage()->markAllPiecesDone();
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
|
// TODO Known issue: if .aria2 file exists, it will not be
|
||||||
|
// deleted on successful verification, because .aria2 file is
|
||||||
|
// not loaded. See also
|
||||||
|
// HttpResponseCommand::handleOtherEncoding()
|
||||||
|
if(getDownloadContext()->isChecksumVerificationNeeded()) {
|
||||||
|
A2_LOG_DEBUG("Verify checksum for zero-length file");
|
||||||
|
SharedHandle<ChecksumCheckIntegrityEntry> entry
|
||||||
|
(new ChecksumCheckIntegrityEntry(getRequestGroup()));
|
||||||
|
entry->initValidator();
|
||||||
|
getDownloadEngine()->getCheckIntegrityMan()->pushEntry(entry);
|
||||||
|
sequence_ = SEQ_EXIT;
|
||||||
|
} else
|
||||||
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
|
{
|
||||||
|
sequence_ = SEQ_DOWNLOAD_ALREADY_COMPLETED;
|
||||||
|
getPieceStorage()->markAllPiecesDone();
|
||||||
|
}
|
||||||
poolConnection();
|
poolConnection();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,7 +74,8 @@
|
||||||
#include "SocketRecvBuffer.h"
|
#include "SocketRecvBuffer.h"
|
||||||
#include "MetalinkHttpEntry.h"
|
#include "MetalinkHttpEntry.h"
|
||||||
#ifdef ENABLE_MESSAGE_DIGEST
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
#include "Checksum.h"
|
# include "Checksum.h"
|
||||||
|
# include "ChecksumCheckIntegrityEntry.h"
|
||||||
#endif // ENABLE_MESSAGE_DIGEST
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
#ifdef HAVE_ZLIB
|
#ifdef HAVE_ZLIB
|
||||||
# include "GZipDecodingStreamFilter.h"
|
# include "GZipDecodingStreamFilter.h"
|
||||||
|
@ -431,18 +432,28 @@ bool HttpResponseCommand::handleOtherEncoding
|
||||||
// For zero-length file, check existing file comparing its size
|
// For zero-length file, check existing file comparing its size
|
||||||
if(!chunkedUsed && getDownloadContext()->knowsTotalLength() &&
|
if(!chunkedUsed && getDownloadContext()->knowsTotalLength() &&
|
||||||
getRequestGroup()->downloadFinishedByFileLength()) {
|
getRequestGroup()->downloadFinishedByFileLength()) {
|
||||||
// TODO If metalink file does not contain size and it contains
|
|
||||||
// hash and file is not zero length, but remote server says the
|
|
||||||
// file size is 0, no hash check is performed in the current
|
|
||||||
// implementation. See also
|
|
||||||
// FtpNegotiationCommand::onFileSizeDetermined()
|
|
||||||
getRequestGroup()->initPieceStorage();
|
getRequestGroup()->initPieceStorage();
|
||||||
getPieceStorage()->markAllPiecesDone();
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
getDownloadContext()->setChecksumVerified(true);
|
// TODO Known issue: if .aria2 file exists, it will not be deleted
|
||||||
A2_LOG_NOTICE
|
// on successful verification, because .aria2 file is not loaded.
|
||||||
(fmt(MSG_DOWNLOAD_ALREADY_COMPLETED,
|
// See also FtpNegotiationCommand::onFileSizeDetermined()
|
||||||
getRequestGroup()->getGID(),
|
if(getDownloadContext()->isChecksumVerificationNeeded()) {
|
||||||
getRequestGroup()->getFirstFilePath().c_str()));
|
A2_LOG_DEBUG("Zero length file exists. Verify checksum.");
|
||||||
|
SharedHandle<ChecksumCheckIntegrityEntry> entry
|
||||||
|
(new ChecksumCheckIntegrityEntry(getRequestGroup()));
|
||||||
|
entry->initValidator();
|
||||||
|
getPieceStorage()->getDiskAdaptor()->openExistingFile();
|
||||||
|
getDownloadEngine()->getCheckIntegrityMan()->pushEntry(entry);
|
||||||
|
} else
|
||||||
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
|
{
|
||||||
|
getPieceStorage()->markAllPiecesDone();
|
||||||
|
getDownloadContext()->setChecksumVerified(true);
|
||||||
|
A2_LOG_NOTICE
|
||||||
|
(fmt(MSG_DOWNLOAD_ALREADY_COMPLETED,
|
||||||
|
getRequestGroup()->getGID(),
|
||||||
|
getRequestGroup()->getFirstFilePath().c_str()));
|
||||||
|
}
|
||||||
poolConnection();
|
poolConnection();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -455,7 +466,22 @@ bool HttpResponseCommand::handleOtherEncoding
|
||||||
// is called. So zero-length file is complete if chunked encoding is
|
// is called. So zero-length file is complete if chunked encoding is
|
||||||
// not used.
|
// not used.
|
||||||
if(!chunkedUsed && getDownloadContext()->knowsTotalLength()) {
|
if(!chunkedUsed && getDownloadContext()->knowsTotalLength()) {
|
||||||
getRequestGroup()->getPieceStorage()->markAllPiecesDone();
|
A2_LOG_DEBUG("File length becomes zero and it means download completed.");
|
||||||
|
// TODO Known issue: if .aria2 file exists, it will not be deleted
|
||||||
|
// on successful verification, because .aria2 file is not loaded.
|
||||||
|
// See also FtpNegotiationCommand::onFileSizeDetermined()
|
||||||
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
|
if(getDownloadContext()->isChecksumVerificationNeeded()) {
|
||||||
|
A2_LOG_DEBUG("Verify checksum for zero-length file");
|
||||||
|
SharedHandle<ChecksumCheckIntegrityEntry> entry
|
||||||
|
(new ChecksumCheckIntegrityEntry(getRequestGroup()));
|
||||||
|
entry->initValidator();
|
||||||
|
getDownloadEngine()->getCheckIntegrityMan()->pushEntry(entry);
|
||||||
|
} else
|
||||||
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
|
{
|
||||||
|
getRequestGroup()->getPieceStorage()->markAllPiecesDone();
|
||||||
|
}
|
||||||
poolConnection();
|
poolConnection();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,23 +61,24 @@ IteratableChecksumValidator::~IteratableChecksumValidator() {}
|
||||||
|
|
||||||
void IteratableChecksumValidator::validateChunk()
|
void IteratableChecksumValidator::validateChunk()
|
||||||
{
|
{
|
||||||
if(!finished()) {
|
// Don't guard with !finished() to allow zero-length file to be
|
||||||
unsigned char buf[4096];
|
// verified.
|
||||||
size_t length = pieceStorage_->getDiskAdaptor()->readData
|
unsigned char buf[4096];
|
||||||
(buf, sizeof(buf), currentOffset_);
|
size_t length = pieceStorage_->getDiskAdaptor()->readData
|
||||||
ctx_->update(buf, length);
|
(buf, sizeof(buf), currentOffset_);
|
||||||
currentOffset_ += length;
|
ctx_->update(buf, length);
|
||||||
if(finished()) {
|
currentOffset_ += length;
|
||||||
std::string actualDigest = ctx_->digest();
|
if(finished()) {
|
||||||
if(dctx_->getDigest() == actualDigest) {
|
std::string actualDigest = ctx_->digest();
|
||||||
pieceStorage_->markAllPiecesDone();
|
if(dctx_->getDigest() == actualDigest) {
|
||||||
} else {
|
pieceStorage_->markAllPiecesDone();
|
||||||
A2_LOG_INFO(fmt("Checksum validation failed. expected=%s, actual=%s",
|
dctx_->setChecksumVerified(true);
|
||||||
util::toHex(dctx_->getDigest()).c_str(),
|
} else {
|
||||||
util::toHex(actualDigest).c_str()));
|
A2_LOG_INFO(fmt("Checksum validation failed. expected=%s, actual=%s",
|
||||||
BitfieldMan bitfield(dctx_->getPieceLength(), dctx_->getTotalLength());
|
util::toHex(dctx_->getDigest()).c_str(),
|
||||||
pieceStorage_->setBitfield(bitfield.getBitfield(), bitfield.getBitfieldLength());
|
util::toHex(actualDigest).c_str()));
|
||||||
}
|
BitfieldMan bitfield(dctx_->getPieceLength(), dctx_->getTotalLength());
|
||||||
|
pieceStorage_->setBitfield(bitfield.getBitfield(), bitfield.getBitfieldLength());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,10 +167,11 @@ RequestGroup::RequestGroup(const SharedHandle<Option>& option)
|
||||||
|
|
||||||
RequestGroup::~RequestGroup() {}
|
RequestGroup::~RequestGroup() {}
|
||||||
|
|
||||||
bool RequestGroup::isCheckIntegrityReady() const
|
bool RequestGroup::isCheckIntegrityReady()
|
||||||
{
|
{
|
||||||
return option_->getAsBool(PREF_CHECK_INTEGRITY) &&
|
return option_->getAsBool(PREF_CHECK_INTEGRITY) &&
|
||||||
(downloadContext_->isChecksumVerificationAvailable() ||
|
((downloadContext_->isChecksumVerificationAvailable() &&
|
||||||
|
downloadFinishedByFileLength()) ||
|
||||||
downloadContext_->isPieceHashVerificationAvailable());
|
downloadContext_->isPieceHashVerificationAvailable());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,27 +253,21 @@ SharedHandle<CheckIntegrityEntry> RequestGroup::createCheckIntegrityEntry()
|
||||||
} else {
|
} else {
|
||||||
checkEntry.reset(new StreamCheckIntegrityEntry(this));
|
checkEntry.reset(new StreamCheckIntegrityEntry(this));
|
||||||
}
|
}
|
||||||
} else if(downloadFinishedByFileLength()) {
|
|
||||||
pieceStorage_->markAllPiecesDone();
|
|
||||||
#ifdef ENABLE_MESSAGE_DIGEST
|
#ifdef ENABLE_MESSAGE_DIGEST
|
||||||
if(option_->getAsBool(PREF_CHECK_INTEGRITY) &&
|
} else if(downloadFinishedByFileLength() &&
|
||||||
downloadContext_->isChecksumVerificationAvailable()) {
|
downloadContext_->isChecksumVerificationAvailable()) {
|
||||||
loadAndOpenFile(infoFile);
|
pieceStorage_->markAllPiecesDone();
|
||||||
ChecksumCheckIntegrityEntry* tempEntry =
|
|
||||||
new ChecksumCheckIntegrityEntry(this);
|
|
||||||
tempEntry->setRedownload(true);
|
|
||||||
checkEntry.reset(tempEntry);
|
|
||||||
} else
|
|
||||||
#endif // ENABLE_MESSAGE_DIGEST
|
|
||||||
{
|
|
||||||
downloadContext_->setChecksumVerified(true);
|
|
||||||
A2_LOG_NOTICE(fmt(MSG_DOWNLOAD_ALREADY_COMPLETED,
|
|
||||||
gid_, downloadContext_->getBasePath().c_str()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
loadAndOpenFile(infoFile);
|
loadAndOpenFile(infoFile);
|
||||||
checkEntry.reset(new StreamCheckIntegrityEntry(this));
|
ChecksumCheckIntegrityEntry* tempEntry =
|
||||||
}
|
new ChecksumCheckIntegrityEntry(this);
|
||||||
|
tempEntry->setRedownload(true);
|
||||||
|
checkEntry.reset(tempEntry);
|
||||||
|
} else
|
||||||
|
#endif // ENABLE_MESSAGE_DIGEST
|
||||||
|
{
|
||||||
|
loadAndOpenFile(infoFile);
|
||||||
|
checkEntry.reset(new StreamCheckIntegrityEntry(this));
|
||||||
|
}
|
||||||
return checkEntry;
|
return checkEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -689,8 +684,6 @@ void RequestGroup::adjustFilename
|
||||||
}
|
}
|
||||||
if(infoFile->exists()) {
|
if(infoFile->exists()) {
|
||||||
// Use current filename
|
// Use current filename
|
||||||
} else if(downloadFinishedByFileLength()) {
|
|
||||||
// File was downloaded already, no need to change file name.
|
|
||||||
} else {
|
} else {
|
||||||
File outfile(getFirstFilePath());
|
File outfile(getFirstFilePath());
|
||||||
if(outfile.exists() && option_->getAsBool(PREF_CONTINUE) &&
|
if(outfile.exists() && option_->getAsBool(PREF_CONTINUE) &&
|
||||||
|
|
|
@ -195,12 +195,13 @@ private:
|
||||||
void removeDefunctControlFile
|
void removeDefunctControlFile
|
||||||
(const SharedHandle<BtProgressInfoFile>& progressInfoFile);
|
(const SharedHandle<BtProgressInfoFile>& progressInfoFile);
|
||||||
|
|
||||||
bool isCheckIntegrityReady() const;
|
|
||||||
public:
|
public:
|
||||||
RequestGroup(const SharedHandle<Option>& option);
|
RequestGroup(const SharedHandle<Option>& option);
|
||||||
|
|
||||||
~RequestGroup();
|
~RequestGroup();
|
||||||
|
|
||||||
|
bool isCheckIntegrityReady();
|
||||||
|
|
||||||
const SharedHandle<SegmentMan>& getSegmentMan() const
|
const SharedHandle<SegmentMan>& getSegmentMan() const
|
||||||
{
|
{
|
||||||
return segmentMan_;
|
return segmentMan_;
|
||||||
|
|
Loading…
Reference in New Issue