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
Tatsuhiro Tsujikawa 2012-01-08 17:46:03 +09:00
parent b6f8a3dbb3
commit 1c292f469e
6 changed files with 121 additions and 71 deletions

View File

@ -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

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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());
} }
} }
} }

View File

@ -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) &&

View File

@ -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_;