diff --git a/src/BitfieldMan.cc b/src/BitfieldMan.cc index 5535af14..9b1033ab 100644 --- a/src/BitfieldMan.cc +++ b/src/BitfieldMan.cc @@ -60,7 +60,7 @@ BitfieldMan::BitfieldMan(size_t blockLength, uint64_t totalLength) cachedFilteredTotalLength_(0) { if(blockLength_ > 0 && totalLength_ > 0) { - blocks_ = totalLength_/blockLength_+(totalLength_%blockLength_ ? 1 : 0); + blocks_ = (totalLength_+blockLength_-1)/blockLength_; bitfieldLength_ = blocks_/8+(blocks_%8 ? 1 : 0); bitfield_ = new unsigned char[bitfieldLength_]; useBitfield_ = new unsigned char[bitfieldLength_]; @@ -772,6 +772,39 @@ bool BitfieldMan::isBitSetOffsetRange(uint64_t offset, uint64_t length) const return true; } +uint64_t BitfieldMan::getOffsetCompletedLength +(uint64_t offset, + uint64_t length) const +{ + uint64_t res = 0; + if(length == 0 || totalLength_ <= offset) { + return 0; + } + if(totalLength_ < offset+length) { + length = totalLength_-offset; + } + size_t start = offset/blockLength_; + size_t end = (offset+length-1)/blockLength_; + if(start == end) { + if(isBitSet(start)) { + res = length; + } + } else { + if(isBitSet(start)) { + res += (start+1)*blockLength_-offset; + } + for(size_t i = start+1; i <= end-1; ++i) { + if(isBitSet(i)) { + res += blockLength_; + } + } + if(isBitSet(end)) { + res += offset+length-end*blockLength_; + } + } + return res; +} + uint64_t BitfieldMan::getMissingUnusedLength(size_t startingIndex) const { if(startingIndex < 0 || blocks_ <= startingIndex) { diff --git a/src/BitfieldMan.h b/src/BitfieldMan.h index 817b9ae8..609e9163 100644 --- a/src/BitfieldMan.h +++ b/src/BitfieldMan.h @@ -264,6 +264,10 @@ public: bool isBitSetOffsetRange(uint64_t offset, uint64_t length) const; + // Returns completed length in bytes in range [offset, + // offset+length). This function will not affected by filter. + uint64_t getOffsetCompletedLength(uint64_t offset, uint64_t length) const; + uint64_t getMissingUnusedLength(size_t startingIndex) const; const unsigned char* getFilterBitfield() const diff --git a/src/FileEntry.cc b/src/FileEntry.cc index 379be62a..7d04e2dd 100644 --- a/src/FileEntry.cc +++ b/src/FileEntry.cc @@ -570,4 +570,26 @@ bool FileEntry::emptyRequestUri() const return uris_.empty() && inFlightRequests_.empty() && requestPool_.empty(); } +void writeFilePath +(std::ostream& o, + const SharedHandle& entry, + bool memory) +{ + if(entry->getPath().empty()) { + std::vector uris; + entry->getUris(uris); + if(uris.empty()) { + o << "n/a"; + } else { + o << uris.front(); + } + } else { + if(memory) { + o << "[MEMORY]" << File(entry->getPath()).getBasename(); + } else { + o << entry->getPath(); + } + } +} + } // namespace aria2 diff --git a/src/FileEntry.h b/src/FileEntry.h index f7d23f38..029bd3e1 100644 --- a/src/FileEntry.h +++ b/src/FileEntry.h @@ -294,6 +294,14 @@ bool isUriSuppliedForRequsetFileEntry(InputIterator first, InputIterator last) return false; } +// Writes filename to given o. If memory is true, the output is +// "[MEMORY]" plus the basename of the filename. If there is no +// FileEntry, writes "n/a" to o. +void writeFilePath +(std::ostream& o, + const SharedHandle& entry, + bool memory); + // Writes first filename to given o. If memory is true, the output is // "[MEMORY]" plus the basename of the first filename. If there is no // FileEntry, writes "n/a" to o. If more than 1 FileEntry are in the @@ -307,20 +315,8 @@ void writeFilePath if(!e) { o << "n/a"; } else { - if(e->getPath().empty()) { - std::vector uris; - e->getUris(uris); - if(uris.empty()) { - o << "n/a"; - } else { - o << uris.front(); - } - } else { - if(memory) { - o << "[MEMORY]" << File(e->getPath()).getBasename(); - } else { - o << e->getPath(); - } + writeFilePath(o, e, memory); + if(!e->getPath().empty()) { size_t count = countRequestedFileEntry(first, last); if(count > 1) { o << " (" << count-1 << "more)"; diff --git a/src/MultiUrlRequestInfo.cc b/src/MultiUrlRequestInfo.cc index 27659aa4..b1d81f47 100644 --- a/src/MultiUrlRequestInfo.cc +++ b/src/MultiUrlRequestInfo.cc @@ -251,7 +251,8 @@ error_code::Value MultiUrlRequestInfo::execute() if(!serverStatOf.empty()) { e->getRequestGroupMan()->saveServerStat(serverStatOf); } - e->getRequestGroupMan()->showDownloadResults(*summaryOut_); + e->getRequestGroupMan()->showDownloadResults + (*summaryOut_, option_->get(PREF_DOWNLOAD_RESULT) == A2_V_FULL); summaryOut_->flush(); RequestGroupMan::DownloadStat s = diff --git a/src/OptionHandlerFactory.cc b/src/OptionHandlerFactory.cc index 87fd87b5..6e2716aa 100644 --- a/src/OptionHandlerFactory.cc +++ b/src/OptionHandlerFactory.cc @@ -205,6 +205,16 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers() op->hide(); handlers.push_back(op); } + { + SharedHandle op(new ParameterOptionHandler + (PREF_DOWNLOAD_RESULT, + TEXT_DOWNLOAD_RESULT, + A2_V_DEFAULT, + A2_V_DEFAULT, + A2_V_FULL)); + op->addTag(TAG_ADVANCED); + handlers.push_back(op); + } #ifdef ENABLE_ASYNC_DNS { SharedHandle op(new BooleanOptionHandler diff --git a/src/RequestGroupMan.cc b/src/RequestGroupMan.cc index 0eef1caf..72d40be4 100644 --- a/src/RequestGroupMan.cc +++ b/src/RequestGroupMan.cc @@ -588,25 +588,32 @@ RequestGroupMan::DownloadStat RequestGroupMan::getDownloadStat() const lastError); } -void RequestGroupMan::showDownloadResults(OutputFile& o) const +void RequestGroupMan::showDownloadResults(OutputFile& o, bool full) const { static const std::string MARK_OK("OK"); static const std::string MARK_ERR("ERR"); static const std::string MARK_INPR("INPR"); static const std::string MARK_RM("RM"); - // Download Results: - // idx|stat|path/length - // ===+====+======================================================================= - o.printf("\n%s" - "\ngid|stat|avg speed |path/URI" - "\n===+====+===========+", - _("Download Results:")); #ifdef __MINGW32__ int pathRowSize = 58; #else // !__MINGW32__ int pathRowSize = 59; #endif // !__MINGW32__ + // Download Results: + // idx|stat|path/length + // ===+====+======================================================================= + o.printf("\n%s" + "\ngid|stat|avg speed |", + _("Download Results:")); + if(full) { + o.write(" %|path/URI" + "\n===+====+===========+===+"); + pathRowSize -= 4; + } else { + o.write("path/URI" + "\n===+====+===========+"); + } std::string line(pathRowSize, '='); o.printf("%s\n", line.c_str()); int ok = 0; @@ -633,8 +640,12 @@ void RequestGroupMan::showDownloadResults(OutputFile& o) const status = MARK_ERR; ++err; } - o.write(formatDownloadResult(status, *itr).c_str()); - o.write("\n"); + if(full) { + formatDownloadResultFull(o, status, *itr); + } else { + o.write(formatDownloadResult(status, *itr).c_str()); + o.write("\n"); + } } if(ok > 0 || err > 0 || inpr > 0 || rm > 0) { o.printf("\n%s\n", _("Status Legend:")); @@ -654,11 +665,12 @@ void RequestGroupMan::showDownloadResults(OutputFile& o) const } } -std::string RequestGroupMan::formatDownloadResult -(const std::string& status, - const DownloadResultHandle& downloadResult) const +namespace { +void formatDownloadResultCommon +(std::ostream& o, + const std::string& status, + const DownloadResultHandle& downloadResult) { - std::stringstream o; o << std::setw(3) << downloadResult->gid << "|" << std::setw(4) << status << "|" << std::setw(11); @@ -670,6 +682,58 @@ std::string RequestGroupMan::formatDownloadResult o << "n/a"; } o << "|"; +} +} // namespace + +void RequestGroupMan::formatDownloadResultFull +(OutputFile& out, + const std::string& status, + const DownloadResultHandle& downloadResult) const +{ + BitfieldMan bt(downloadResult->pieceLength, downloadResult->totalLength); + bt.setBitfield(reinterpret_cast + (util::fromHex(downloadResult->bitfieldStr).data()), + downloadResult->bitfieldStr.size()/2); + bool head = true; + const std::vector >& fileEntries = + downloadResult->fileEntries; + for(std::vector >::const_iterator i = + fileEntries.begin(), eoi = fileEntries.end(); i != eoi; ++i) { + if(!(*i)->isRequested()) { + continue; + } + std::stringstream o; + if(head) { + formatDownloadResultCommon(o, status, downloadResult); + head = false; + } else { + o << " | | |"; + } + if((*i)->getLength() == 0 || downloadResult->bitfieldStr.empty()) { + o << " -|"; + } else { + uint64_t completedLength = + bt.getOffsetCompletedLength((*i)->getOffset(), (*i)->getLength()); + o << std::setw(3) << 100*completedLength/(*i)->getLength() << "|"; + } + writeFilePath(o, *i, downloadResult->inMemoryDownload); + o << "\n"; + out.write(o.str().c_str()); + } + if(head) { + std::stringstream o; + formatDownloadResultCommon(o, status, downloadResult); + o << " -|n/a\n"; + out.write(o.str().c_str()); + } +} + +std::string RequestGroupMan::formatDownloadResult +(const std::string& status, + const DownloadResultHandle& downloadResult) const +{ + std::stringstream o; + formatDownloadResultCommon(o, status, downloadResult); const std::vector >& fileEntries = downloadResult->fileEntries; writeFilePath(fileEntries.begin(), fileEntries.end(), o, diff --git a/src/RequestGroupMan.h b/src/RequestGroupMan.h index fc243f05..7859b634 100644 --- a/src/RequestGroupMan.h +++ b/src/RequestGroupMan.h @@ -85,6 +85,11 @@ private: size_t maxDownloadResult_; + void formatDownloadResultFull + (OutputFile& out, + const std::string& status, + const DownloadResultHandle& downloadResult) const; + std::string formatDownloadResult (const std::string& status, const DownloadResultHandle& downloadResult) const; @@ -160,7 +165,7 @@ public: bool removeReservedGroup(a2_gid_t gid); - void showDownloadResults(OutputFile& o) const; + void showDownloadResults(OutputFile& o, bool full) const; bool isSameFileBeingDownloaded(RequestGroup* requestGroup) const; diff --git a/src/prefs.cc b/src/prefs.cc index b50d5f90..748d2c7e 100644 --- a/src/prefs.cc +++ b/src/prefs.cc @@ -45,6 +45,7 @@ const std::string A2_V_DEFAULT("default"); const std::string V_NONE("none"); const std::string V_MEM("mem"); const std::string V_ALL("all"); +const std::string A2_V_FULL("full"); /** * General preferences @@ -218,6 +219,8 @@ const std::string PREF_STREAM_PIECE_SELECTOR("stream-piece-selector"); const std::string PREF_TRUNCATE_CONSOLE_READOUT("truncate-console-readout"); // value: true | false const std::string PREF_PAUSE("pause"); +// value: default | full +const std::string PREF_DOWNLOAD_RESULT("download-result"); /** * FTP related preferences diff --git a/src/prefs.h b/src/prefs.h index bd911c1c..713d4f8f 100644 --- a/src/prefs.h +++ b/src/prefs.h @@ -49,6 +49,7 @@ extern const std::string A2_V_DEFAULT; extern const std::string V_NONE; extern const std::string V_MEM; extern const std::string V_ALL; +extern const std::string A2_V_FULL; /** * General preferences */ @@ -221,6 +222,8 @@ extern const std::string PREF_STREAM_PIECE_SELECTOR; extern const std::string PREF_TRUNCATE_CONSOLE_READOUT; // value: true | false extern const std::string PREF_PAUSE; +// value: default | full +extern const std::string PREF_DOWNLOAD_RESULT; /** * FTP related preferences diff --git a/src/usage_text.h b/src/usage_text.h index 3f374ead..a465e908 100644 --- a/src/usage_text.h +++ b/src/usage_text.h @@ -806,3 +806,15 @@ #define TEXT_RPC_ALLOW_ORIGIN_ALL \ _(" --rpc-allow-origin-all[=true|false] Add Access-Control-Allow-Origin header\n" \ " field with value '*' to the RPC response.") +#define TEXT_DOWNLOAD_RESULT \ + _(" --download-result=OPT This option changes the way \"Download Results\"\n" \ + " is formatted. If OPT is 'default', print GID,\n" \ + " status, average download speed and path/URI. If\n" \ + " multiple files are involved, path/URI of first\n" \ + " requested file is printed and remaining ones are\n" \ + " omitted.\n" \ + " If OPT is 'full', print GID, status, average\n" \ + " download speed, percentage of progress and\n" \ + " path/URI. The percentage of progress and\n" \ + " path/URI are printed for each requested file in\n" \ + " each row.") diff --git a/test/BitfieldManTest.cc b/test/BitfieldManTest.cc index 8fb975b1..82659a1d 100644 --- a/test/BitfieldManTest.cc +++ b/test/BitfieldManTest.cc @@ -26,6 +26,7 @@ class BitfieldManTest:public CppUnit::TestFixture { CPPUNIT_TEST(testGetSparseMissingUnusedIndex_setBit); CPPUNIT_TEST(testGetSparseMissingUnusedIndex_withMinSplitSize); CPPUNIT_TEST(testIsBitSetOffsetRange); + CPPUNIT_TEST(testGetOffsetCompletedLength); CPPUNIT_TEST(testGetMissingUnusedLength); CPPUNIT_TEST(testSetBitRange); CPPUNIT_TEST(testGetAllMissingIndexes); @@ -57,6 +58,7 @@ public: void testGetSparseMissingUnusedIndex_setBit(); void testGetSparseMissingUnusedIndex_withMinSplitSize(); void testIsBitSetOffsetRange(); + void testGetOffsetCompletedLength(); void testGetMissingUnusedLength(); void testSetBitRange(); void testCountFilteredBlock(); @@ -445,6 +447,28 @@ void BitfieldManTest::testIsBitSetOffsetRange() CPPUNIT_ASSERT(!bitfield.isBitSetOffsetRange(pieceLength*100, pieceLength*3)); } +void BitfieldManTest::testGetOffsetCompletedLength() +{ + BitfieldMan bt(1024, 1024*20); + // 00000|00000|00000|00000 + CPPUNIT_ASSERT_EQUAL((uint64_t)0, bt.getOffsetCompletedLength(0, 1024)); + CPPUNIT_ASSERT_EQUAL((uint64_t)0, bt.getOffsetCompletedLength(0, 0)); + for(size_t i = 2; i <= 4; ++i) { + bt.setBit(i); + } + // 00111|00000|00000|00000 + CPPUNIT_ASSERT_EQUAL((uint64_t)3072, bt.getOffsetCompletedLength(2048, 3072)); + CPPUNIT_ASSERT_EQUAL((uint64_t)3071, bt.getOffsetCompletedLength(2047, 3072)); + CPPUNIT_ASSERT_EQUAL((uint64_t)3071, bt.getOffsetCompletedLength(2049, 3072)); + CPPUNIT_ASSERT_EQUAL((uint64_t)0, bt.getOffsetCompletedLength(2048, 0)); + CPPUNIT_ASSERT_EQUAL((uint64_t)1, bt.getOffsetCompletedLength(2048, 1)); + CPPUNIT_ASSERT_EQUAL((uint64_t)0, bt.getOffsetCompletedLength(2047, 1)); + CPPUNIT_ASSERT_EQUAL((uint64_t)3072, bt.getOffsetCompletedLength(0, 1024*20)); + CPPUNIT_ASSERT_EQUAL((uint64_t)3072, + bt.getOffsetCompletedLength(0, 1024*20+10)); + CPPUNIT_ASSERT_EQUAL((uint64_t)0, bt.getOffsetCompletedLength(1024*20, 1)); +} + void BitfieldManTest::testGetMissingUnusedLength() { uint64_t totalLength = 1024*10+10;