From eed0406484ce81e1154aaf61103814c7fa4a8fe0 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 13 Nov 2008 13:40:40 +0000 Subject: [PATCH] 2008-11-13 Tatsuhiro Tsujikawa Rewritten URI handling functions. They are now provided as a testable functions. * src/Makefile.am * src/RequestGroup.cc * src/RequestGroup.h * src/download_helper.cc * src/download_helper.h * src/main.cc * test/DownloadHelperTest.cc * test/Makefile.am * test/input_uris.txt --- ChangeLog | 14 ++ src/Makefile.am | 3 +- src/Makefile.in | 6 +- src/RequestGroup.cc | 8 +- src/RequestGroup.h | 2 + src/download_helper.cc | 308 ++++++++++++++++++++++++++++++ src/download_helper.h | 76 ++++++++ src/main.cc | 273 +------------------------- test/DownloadHelperTest.cc | 380 +++++++++++++++++++++++++++++++++++++ test/Makefile.am | 5 +- test/Makefile.in | 20 +- test/input_uris.txt | 5 + 12 files changed, 822 insertions(+), 278 deletions(-) create mode 100644 src/download_helper.cc create mode 100644 src/download_helper.h create mode 100644 test/DownloadHelperTest.cc create mode 100644 test/input_uris.txt diff --git a/ChangeLog b/ChangeLog index c2430f72..b496b2b2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2008-11-13 Tatsuhiro Tsujikawa + + Rewritten URI handling functions. They are now provided as a testable + functions. + * src/Makefile.am + * src/RequestGroup.cc + * src/RequestGroup.h + * src/download_helper.cc + * src/download_helper.h + * src/main.cc + * test/DownloadHelperTest.cc + * test/Makefile.am + * test/input_uris.txt + 2008-11-12 Tatsuhiro Tsujikawa Quickly terminate commands when ctrl-c is pressed. diff --git a/src/Makefile.am b/src/Makefile.am index 618fd5ca..297c1ab2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,7 +1,8 @@ bin_PROGRAMS = aria2c aria2c_SOURCES = main.cc\ option_processing.cc\ - version_usage.cc + version_usage.cc\ + download_helper.cc download_helper.h SRCS = Socket.h\ SocketCore.cc SocketCore.h\ BinaryStream.h\ diff --git a/src/Makefile.in b/src/Makefile.in index f030dc18..9a3be861 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -828,7 +828,7 @@ am__installdirs = "$(DESTDIR)$(bindir)" binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) am_aria2c_OBJECTS = main.$(OBJEXT) option_processing.$(OBJEXT) \ - version_usage.$(OBJEXT) + version_usage.$(OBJEXT) download_helper.$(OBJEXT) aria2c_OBJECTS = $(am_aria2c_OBJECTS) aria2c_DEPENDENCIES = libaria2c.a @ALLOCA@ DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) @@ -1025,7 +1025,8 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ aria2c_SOURCES = main.cc\ option_processing.cc\ - version_usage.cc + version_usage.cc\ + download_helper.cc download_helper.h SRCS = Socket.h SocketCore.cc SocketCore.h BinaryStream.h Command.cc \ Command.h AbstractCommand.cc AbstractCommand.h \ @@ -1523,6 +1524,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/VersionMetalinkParserState.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/XML2SAXMetalinkProcessor.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/asctime_r.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/download_helper.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gai_strerror.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getaddrinfo.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gettimeofday.Po@am__quote@ diff --git a/src/RequestGroup.cc b/src/RequestGroup.cc index 2124164f..355d0d16 100644 --- a/src/RequestGroup.cc +++ b/src/RequestGroup.cc @@ -113,7 +113,7 @@ RequestGroup::RequestGroup(const Option* option, const std::deque& uris): _gid(++_gidCounter), _uris(uris), - _numConcurrentCommand(0), + _numConcurrentCommand(option->getAsInt(PREF_SPLIT)), _numStreamConnection(0), _numCommand(0), _segmentManFactory(new DefaultSegmentManFactory(option)), @@ -501,6 +501,7 @@ void RequestGroup::createNextCommandWithAdj(std::deque& commands, numCommand = 1+numAdj; } else { if(_numConcurrentCommand == 0) { + // TODO remove _uris.size() support numCommand = _uris.size(); } else { numCommand = _numConcurrentCommand; @@ -1074,4 +1075,9 @@ void RequestGroup::increaseAndValidateFileNotFoundCount() } } +unsigned int RequestGroup::getNumConcurrentCommand() const +{ + return _numConcurrentCommand; +} + } // namespace aria2 diff --git a/src/RequestGroup.h b/src/RequestGroup.h index 83b175f2..7b37b9a1 100644 --- a/src/RequestGroup.h +++ b/src/RequestGroup.h @@ -213,6 +213,8 @@ public: _numConcurrentCommand = num; } + unsigned int getNumConcurrentCommand() const; + int32_t getGID() const { return _gid; diff --git a/src/download_helper.cc b/src/download_helper.cc new file mode 100644 index 00000000..177faa9b --- /dev/null +++ b/src/download_helper.cc @@ -0,0 +1,308 @@ +/* */ +#include "download_helper.h" + +#include +#include +#include + +#include "RequestGroup.h" +#include "Option.h" +#include "prefs.h" +#include "Metalink2RequestGroup.h" +#include "ProtocolDetector.h" +#include "ParameterizedStringParser.h" +#include "PStringBuildVisitor.h" +#include "UriListParser.h" +#include "SingleFileDownloadContext.h" +#include "RecoverableException.h" +#include "FatalException.h" +#include "message.h" +#include "StringFormat.h" +#include "DefaultBtContext.h" +#include "FileEntry.h" +#include "LogFactory.h" +#include "File.h" + +namespace aria2 { + +static void unfoldURI +(std::deque& result, const std::deque& args) +{ + ParameterizedStringParser p; + PStringBuildVisitor v; + for(std::deque::const_iterator itr = args.begin(); + itr != args.end(); ++itr) { + v.reset(); + p.parse(*itr)->accept(&v); + result.insert(result.end(), v.getURIs().begin(), v.getURIs().end()); + } +} + +static void splitURI(std::deque& result, + std::deque::const_iterator begin, + std::deque::const_iterator end, + size_t numSplit) +{ + size_t numURIs = std::distance(begin, end); + if(numURIs >= numSplit) { + result.insert(result.end(), begin, end); + } else { + for(size_t i = 0; i < numSplit/numURIs; ++i) { + result.insert(result.end(), begin, end); + } + result.insert(result.end(), begin, begin+(numSplit%numURIs)); + } +} + +static SharedHandle createRequestGroup +(const Option* op, const std::deque& uris, + const Option& requestOption, + bool useOutOption = false) +{ + SharedHandle rg(new RequestGroup(op, uris)); + SharedHandle dctx + (new SingleFileDownloadContext(op->getAsInt(PREF_SEGMENT_SIZE), + 0, + A2STR::NIL, + useOutOption? + requestOption.get(PREF_OUT):A2STR::NIL)); + dctx->setDir(requestOption.get(PREF_DIR)); + rg->setDownloadContext(dctx); + return rg; +} + +#ifdef ENABLE_BITTORRENT + +static +SharedHandle +createBtRequestGroup(const std::string& torrentFilePath, + Option* op, + const std::deque& auxUris, + const Option& requestOption) +{ + SharedHandle rg(new RequestGroup(op, auxUris)); + SharedHandle btContext(new DefaultBtContext()); + btContext->load(torrentFilePath);// may throw exception + if(op->defined(PREF_PEER_ID_PREFIX)) { + btContext->setPeerIdPrefix(op->get(PREF_PEER_ID_PREFIX)); + } + btContext->setDir(requestOption.get(PREF_DIR)); + rg->setDownloadContext(btContext); + btContext->setOwnerRequestGroup(rg.get()); + return rg; +} + +void createRequestGroupForBitTorrent +(std::deque >& result, Option* op, + const std::deque& uris) +{ + std::deque nargs; + if(op->get(PREF_PARAMETERIZED_URI) == V_TRUE) { + unfoldURI(nargs, uris); + } else { + nargs = uris; + } + // we ignore -Z option here + size_t numSplit = op->getAsInt(PREF_SPLIT); + std::deque auxUris; + splitURI(auxUris, nargs.begin(), nargs.end(), numSplit); + SharedHandle rg = + createBtRequestGroup(op->get(PREF_TORRENT_FILE), op, auxUris, *op); + rg->setNumConcurrentCommand(numSplit); + result.push_back(rg); +} + +#endif // ENABLE_BITTORRENT + +#ifdef ENABLE_METALINK +void createRequestGroupForMetalink +(std::deque >& result, Option* op) +{ + Metalink2RequestGroup(op).generate(result, op->get(PREF_METALINK_FILE), *op); + if(result.empty()) { + throw FatalException("No files to download."); + } +} +#endif // ENABLE_METALINK + +class AccRequestGroup { +private: + std::deque >& _requestGroups; + ProtocolDetector _detector; + Option* _op; + const Option& _requestOption; +public: + AccRequestGroup(std::deque >& requestGroups, + Option* op, + const Option& requestOption): + _requestGroups(requestGroups), _op(op), _requestOption(requestOption) {} + + void + operator()(const std::string& uri) + { + if(_detector.isStreamProtocol(uri)) { + std::deque streamURIs; + size_t numSplit = _op->getAsInt(PREF_SPLIT); + for(size_t i = 0; i < numSplit; ++i) { + streamURIs.push_back(uri); + } + SharedHandle rg = + createRequestGroup(_op, streamURIs, _requestOption); + rg->setNumConcurrentCommand(numSplit); + _requestGroups.push_back(rg); + } +#ifdef ENABLE_BITTORRENT + else if(_detector.guessTorrentFile(uri)) { + try { + _requestGroups.push_back(createBtRequestGroup(uri, _op, + std::deque(), + _requestOption)); + } catch(RecoverableException& e) { + // error occurred while parsing torrent file. + // We simply ignore it. + LogFactory::getInstance()->error(EX_EXCEPTION_CAUGHT, e); + } + } +#endif // ENABLE_BITTORRENT +#ifdef ENABLE_METALINK + else if(_detector.guessMetalinkFile(uri)) { + try { + Metalink2RequestGroup(_op).generate(_requestGroups, uri, + _requestOption); + } catch(RecoverableException& e) { + // error occurred while parsing metalink file. + // We simply ignore it. + LogFactory::getInstance()->error(EX_EXCEPTION_CAUGHT, e); + } + } +#endif // ENABLE_METALINK + else { + LogFactory::getInstance()->error(MSG_UNRECOGNIZED_URI, (uri).c_str()); + } + } +}; + +class StreamProtocolFilter { +private: + ProtocolDetector _detector; +public: + bool operator()(const std::string& uri) { + return _detector.isStreamProtocol(uri); + } +}; + +static void copyIfndef(Option& dest, const Option& src, const std::string& name) +{ + if(!dest.defined(name)) { + dest.put(name, src.get(name)); + } +} + +static void createRequestGroupForUri +(std::deque >& result, Option* op, + const std::deque& uris, const Option& requestOption) +{ + std::deque nargs; + if(op->get(PREF_PARAMETERIZED_URI) == V_TRUE) { + unfoldURI(nargs, uris); + } else { + nargs = uris; + } + if(op->get(PREF_FORCE_SEQUENTIAL) == V_TRUE) { + std::for_each(nargs.begin(), nargs.end(), + AccRequestGroup(result, op, requestOption)); + } else { + std::deque::iterator strmProtoEnd = + std::stable_partition(nargs.begin(), nargs.end(), StreamProtocolFilter()); + // let's process http/ftp protocols first. + if(nargs.begin() != strmProtoEnd) { + size_t numSplit = op->getAsInt(PREF_SPLIT); + std::deque streamURIs; + splitURI(streamURIs, nargs.begin(), strmProtoEnd, + numSplit); + SharedHandle rg = + createRequestGroup(op, streamURIs, requestOption, true); + rg->setNumConcurrentCommand(numSplit); + result.push_back(rg); + } + // process remaining URIs(local metalink, BitTorrent files) + std::for_each(strmProtoEnd, nargs.end(), + AccRequestGroup(result, op, requestOption)); + } +} + +void createRequestGroupForUri +(std::deque >& result, Option* op, + const std::deque& uris) +{ + createRequestGroupForUri(result, op, uris, *op); +} + +static void createRequestGroupForUriList +(std::deque >& result, Option* op, std::istream& in) +{ + UriListParser p(in); + while(p.hasNext()) { + std::deque uris; + Option requestOption; + p.parseNext(uris, requestOption); + if(uris.empty()) { + continue; + } + copyIfndef(requestOption, *op, PREF_DIR); + copyIfndef(requestOption, *op, PREF_OUT); + + createRequestGroupForUri(result, op, uris, requestOption); + } +} + +void createRequestGroupForUriList +(std::deque >& result, Option* op) +{ + if(op->get(PREF_INPUT_FILE) == "-") { + createRequestGroupForUriList(result, op, std::cin); + } else { + if(!File(op->get(PREF_INPUT_FILE)).isFile()) { + throw FatalException + (StringFormat(EX_FILE_OPEN, op->get(PREF_INPUT_FILE).c_str(), + "No such file").str()); + } + std::ifstream f(op->get(PREF_INPUT_FILE).c_str()); + createRequestGroupForUriList(result, op, f); + } +} + +} // namespace aria2 diff --git a/src/download_helper.h b/src/download_helper.h new file mode 100644 index 00000000..23f51d02 --- /dev/null +++ b/src/download_helper.h @@ -0,0 +1,76 @@ +/* */ +#ifndef _D_DOWNLOAD_HELPER_H_ +#define _D_DOWNLOAD_HELPER_H_ + +#include "common.h" + +#include +#include + +#include "SharedHandle.h" + +namespace aria2 { + +class RequestGroup; +class Option; + +// Create RequestGroup object using torrent file specified by torrent-file +// option. In this function, force-sequential is ignored. +void createRequestGroupForBitTorrent +(std::deque >& result, Option* op, + const std::deque& uris); + +// Create RequestGroup objects using Metalink file specified by metalink-file +// option. +void createRequestGroupForMetalink +(std::deque >& result, Option* op); + +// Create RequestGroup objects from reading file specified by input-file option. +// If the value of input-file option is "-", stdin is used as a input source. +// Each line is treated as if it is provided in command-line argument. +// The additional out and dir options can be specified after each line of URIs. +// This optional line must start with white space(s). +void createRequestGroupForUriList +(std::deque >& result, Option* op); + +// Create RequestGroup object using provided uris. +void createRequestGroupForUri +(std::deque >& result, Option* op, + const std::deque& uris); + +} // namespace aria2 + +#endif // _D_DOWNLOAD_HELPER_H_ diff --git a/src/main.cc b/src/main.cc index c000b1b8..869e2563 100644 --- a/src/main.cc +++ b/src/main.cc @@ -51,9 +51,7 @@ #include "FeatureConfig.h" #include "MultiUrlRequestInfo.h" #include "SimpleRandomizer.h" -#include "FatalException.h" #include "File.h" -#include "UriListParser.h" #include "message.h" #include "prefs.h" #include "Option.h" @@ -61,21 +59,14 @@ #include "a2io.h" #include "a2time.h" #include "Platform.h" -#include "ParameterizedStringParser.h" -#include "PStringBuildVisitor.h" -#include "SingleFileDownloadContext.h" #include "DefaultBtContext.h" #include "FileEntry.h" #include "RequestGroup.h" -#include "ProtocolDetector.h" #include "ConsoleStatCalc.h" #include "NullStatCalc.h" -#include "StringFormat.h" -#include "A2STR.h" -#include "RecoverableException.h" +#include "download_helper.h" #ifdef ENABLE_METALINK # include "MetalinkHelper.h" -# include "Metalink2RequestGroup.h" # include "MetalinkEntry.h" #endif // ENABLE_METALINK #ifdef ENABLE_MESSAGE_DIGEST @@ -90,35 +81,6 @@ namespace aria2 { // output stream to /dev/null std::ofstream nullout(DEV_NULL); -std::deque unfoldURI(const std::deque& args) -{ - std::deque nargs; - ParameterizedStringParser p; - PStringBuildVisitor v; - for(std::deque::const_iterator itr = args.begin(); itr != args.end(); - ++itr) { - v.reset(); - p.parse(*itr)->accept(&v); - nargs.insert(nargs.end(), v.getURIs().begin(), v.getURIs().end()); - } - return nargs; -} - -RequestGroupHandle createRequestGroup -(const Option* op, const std::deque& uris, - const Option& requestOption) -{ - RequestGroupHandle rg(new RequestGroup(op, uris)); - SingleFileDownloadContextHandle dctx - (new SingleFileDownloadContext(op->getAsInt(PREF_SEGMENT_SIZE), - 0, - A2STR::NIL, - requestOption.get(PREF_OUT))); - dctx->setDir(requestOption.get(PREF_DIR)); - rg->setDownloadContext(dctx); - return rg; -} - SharedHandle getStatCalc(const Option* op) { SharedHandle statCalc; @@ -141,227 +103,6 @@ std::ostream& getSummaryOut(const Option* op) extern Option* option_processing(int argc, char* const argv[]); -#ifdef ENABLE_BITTORRENT - -SharedHandle -createBtRequestGroup(const std::string& torrentFilePath, - Option* op, - const std::deque& auxUris, - const Option& requestOption) -{ - SharedHandle rg(new RequestGroup(op, auxUris)); - SharedHandle btContext(new DefaultBtContext()); - btContext->load(torrentFilePath);// may throw exception - if(op->defined(PREF_PEER_ID_PREFIX)) { - btContext->setPeerIdPrefix(op->get(PREF_PEER_ID_PREFIX)); - } - btContext->setDir(requestOption.get(PREF_DIR)); - rg->setDownloadContext(btContext); - btContext->setOwnerRequestGroup(rg.get()); - return rg; -} - -int32_t downloadBitTorrent(Option* op, const std::deque& uris) -{ - std::deque nargs; - if(op->get(PREF_PARAMETERIZED_URI) == V_TRUE) { - nargs = unfoldURI(uris); - } else { - nargs = uris; - } - RequestGroups groups; - size_t numSplit = op->getAsInt(PREF_SPLIT); - if(nargs.size() >= numSplit) { - RequestGroupHandle rg = createBtRequestGroup(op->get(PREF_TORRENT_FILE), - op, nargs, *op); - rg->setNumConcurrentCommand(numSplit); - groups.push_back(rg); - } else { - std::deque xargs; - if(!nargs.empty()) { - ncopy(nargs.begin(), nargs.end(), numSplit, std::back_inserter(xargs)); - xargs.erase(xargs.begin()+numSplit, xargs.end()); - } - RequestGroupHandle rg = createBtRequestGroup(op->get(PREF_TORRENT_FILE), - op, xargs, *op); - rg->setNumConcurrentCommand(numSplit); - groups.push_back(rg); - } - return MultiUrlRequestInfo(groups, op, getStatCalc(op), getSummaryOut(op)).execute(); -} -#endif // ENABLE_BITTORRENT - -#ifdef ENABLE_METALINK -int32_t downloadMetalink(Option* op) -{ - RequestGroups groups; - Metalink2RequestGroup(op).generate(groups, op->get(PREF_METALINK_FILE), *op); - if(groups.empty()) { - throw FatalException("No files to download."); - } - return MultiUrlRequestInfo(groups, op, getStatCalc(op), getSummaryOut(op)).execute(); -} -#endif // ENABLE_METALINK - -class AccRequestGroup { -private: - std::deque >& _requestGroups; - ProtocolDetector _detector; - Option* _op; - Option& _requestOption; -public: - AccRequestGroup(std::deque >& requestGroups, - Option* op, - Option& requestOption): - _requestGroups(requestGroups), _op(op), _requestOption(requestOption) {} - - void - operator()(const std::string& uri) - { - if(_detector.isStreamProtocol(uri)) { - std::deque xuris; - for(size_t count = _op->getAsInt(PREF_SPLIT); count; --count) { - xuris.push_back(uri); - } - RequestGroupHandle rg = createRequestGroup(_op, xuris, _requestOption); - _requestGroups.push_back(rg); - } -#ifdef ENABLE_BITTORRENT - else if(_detector.guessTorrentFile(uri)) { - try { - _requestGroups.push_back(createBtRequestGroup(uri, _op, - std::deque(), - _requestOption)); - } catch(RecoverableException& e) { - // error occurred while parsing torrent file. - // We simply ignore it. - LogFactory::getInstance()->error(EX_EXCEPTION_CAUGHT, e); - } - } -#endif // ENABLE_BITTORRENT -#ifdef ENABLE_METALINK - else if(_detector.guessMetalinkFile(uri)) { - try { - Metalink2RequestGroup(_op).generate(_requestGroups, uri, - _requestOption); - } catch(RecoverableException& e) { - // error occurred while parsing metalink file. - // We simply ignore it. - LogFactory::getInstance()->error(EX_EXCEPTION_CAUGHT, e); - } - } -#endif // ENABLE_METALINK - else { - LogFactory::getInstance()->error(MSG_UNRECOGNIZED_URI, (uri).c_str()); - } - } -}; - -void copyIfndef(Option& dest, const Option& src, const std::string& name) -{ - if(!dest.defined(name)) { - dest.put(name, src.get(name)); - } -} - -int32_t downloadUriList(Option* op, std::istream& in) -{ - UriListParser p(in); - RequestGroups groups; - while(p.hasNext()) { - std::deque uris; - Option requestOption; - p.parseNext(uris, requestOption); - copyIfndef(requestOption, *op, PREF_DIR); - copyIfndef(requestOption, *op, PREF_OUT); - if(uris.size() == 1 && op->get(PREF_PARAMETERIZED_URI) == V_TRUE) { - std::deque unfoldedURIs = unfoldURI(uris); - std::for_each(unfoldedURIs.begin(), unfoldedURIs.end(), - AccRequestGroup(groups, op, requestOption)); - } else if(uris.size() == 1) { - std::for_each(uris.begin(), uris.end(), - AccRequestGroup(groups, op, requestOption)); - } else if(!uris.empty()) { - size_t numSplit = op->getAsInt(PREF_SPLIT); - if(uris.size() >= numSplit) { - SharedHandle rg = - createRequestGroup(op, uris, requestOption); - rg->setNumConcurrentCommand(numSplit); - groups.push_back(rg); - } else { - std::deque xuris; - ncopy(uris.begin(), uris.end(), numSplit, std::back_inserter(xuris)); - xuris.erase(xuris.begin()+numSplit, xuris.end()); - SharedHandle rg = - createRequestGroup(op, xuris, requestOption); - rg->setNumConcurrentCommand(numSplit); - groups.push_back(rg); - } - } - } - return MultiUrlRequestInfo(groups, op, getStatCalc(op), getSummaryOut(op)).execute(); -} - -int32_t downloadUriList(Option* op) -{ - if(op->get(PREF_INPUT_FILE) == "-") { - return downloadUriList(op, std::cin); - } else { - if(!File(op->get(PREF_INPUT_FILE)).isFile()) { - throw FatalException - (StringFormat(EX_FILE_OPEN, op->get(PREF_INPUT_FILE).c_str(), - "No such file").str()); - } - std::ifstream f(op->get(PREF_INPUT_FILE).c_str()); - return downloadUriList(op, f); - } -} - -class StreamProtocolFilter { -private: - ProtocolDetector _detector; -public: - bool operator()(const std::string& uri) { - return _detector.isStreamProtocol(uri); - } -}; - -int32_t downloadUri(Option* op, const std::deque& uris) -{ - std::deque nargs; - if(op->get(PREF_PARAMETERIZED_URI) == V_TRUE) { - nargs = unfoldURI(uris); - } else { - nargs = uris; - } - RequestGroups groups; - if(op->get(PREF_FORCE_SEQUENTIAL) == V_TRUE) { - std::for_each(nargs.begin(), nargs.end(), AccRequestGroup(groups, op, *op)); - } else { - std::deque::iterator strmProtoEnd = - std::stable_partition(nargs.begin(), nargs.end(), StreamProtocolFilter()); - // let's process http/ftp protocols first. - size_t numSplit = op->getAsInt(PREF_SPLIT); - size_t numURIs = std::distance(nargs.begin(), strmProtoEnd); - if(numURIs >= numSplit) { - std::deque xargs(nargs.begin(), strmProtoEnd); - RequestGroupHandle rg = createRequestGroup(op, xargs, *op); - rg->setNumConcurrentCommand(numSplit); - groups.push_back(rg); - } else if(numURIs > 0) { - std::deque xargs; - ncopy(nargs.begin(), strmProtoEnd, numSplit, std::back_inserter(xargs)); - xargs.erase(xargs.begin()+numSplit, xargs.end()); - RequestGroupHandle rg = createRequestGroup(op, xargs, *op); - rg->setNumConcurrentCommand(numSplit); - groups.push_back(rg); - } - // process remaining URIs(local metalink, BitTorrent files) - std::for_each(strmProtoEnd, nargs.end(), AccRequestGroup(groups, op, *op)); - } - return MultiUrlRequestInfo(groups, op, getStatCalc(op), getSummaryOut(op)).execute(); -} - int main(int argc, char* argv[]) { Option* op = option_processing(argc, argv); @@ -397,6 +138,7 @@ int main(int argc, char* argv[]) Util::setGlobalSignalHandler(SIGPIPE, SIG_IGN, 0); #endif int32_t returnValue = 0; + std::deque > requestGroups; #ifdef ENABLE_BITTORRENT if(op->defined(PREF_TORRENT_FILE)) { if(op->get(PREF_SHOW_FILES) == V_TRUE) { @@ -404,7 +146,7 @@ int main(int argc, char* argv[]) btContext->load(op->get(PREF_TORRENT_FILE)); std::cout << btContext << std::endl; } else { - returnValue = downloadBitTorrent(op, args); + createRequestGroupForBitTorrent(requestGroups, op, args); } } else @@ -419,16 +161,19 @@ int main(int argc, char* argv[]) MetalinkEntry::toFileEntry(fileEntries, metalinkEntries); Util::toStream(std::cout, fileEntries); } else { - returnValue = downloadMetalink(op); + createRequestGroupForMetalink(requestGroups, op); } } else #endif // ENABLE_METALINK if(op->defined(PREF_INPUT_FILE)) { - returnValue = downloadUriList(op); + createRequestGroupForUriList(requestGroups, op); } else { - returnValue = downloadUri(op, args); + createRequestGroupForUri(requestGroups, op, args); } + + returnValue = MultiUrlRequestInfo(requestGroups, op, getStatCalc(op), + getSummaryOut(op)).execute(); if(returnValue == 1) { exitStatus = EXIT_FAILURE; } diff --git a/test/DownloadHelperTest.cc b/test/DownloadHelperTest.cc new file mode 100644 index 00000000..77a8c56e --- /dev/null +++ b/test/DownloadHelperTest.cc @@ -0,0 +1,380 @@ +#include "download_helper.h" + +#include +#include +#include +#include + +#include + +#include "RequestGroup.h" +#include "DownloadContext.h" +#include "Option.h" +#include "array_fun.h" +#include "prefs.h" +#include "Exception.h" +#include "Util.h" + +namespace aria2 { + +class DownloadHelperTest:public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(DownloadHelperTest); + CPPUNIT_TEST(testCreateRequestGroupForUri); + CPPUNIT_TEST(testCreateRequestGroupForUri_parameterized); + CPPUNIT_TEST(testCreateRequestGroupForUri_BitTorrent); + CPPUNIT_TEST(testCreateRequestGroupForUri_Metalink); + CPPUNIT_TEST(testCreateRequestGroupForUriList); + CPPUNIT_TEST(testCreateRequestGroupForBitTorrent); + CPPUNIT_TEST(testCreateRequestGroupForMetalink); + CPPUNIT_TEST_SUITE_END(); +public: + void setUp() {} + + void tearDown() {} + + void testCreateRequestGroupForUri(); + void testCreateRequestGroupForUri_parameterized(); + void testCreateRequestGroupForUri_BitTorrent(); + void testCreateRequestGroupForUri_Metalink(); + void testCreateRequestGroupForUriList(); + void testCreateRequestGroupForBitTorrent(); + void testCreateRequestGroupForMetalink(); +}; + + +CPPUNIT_TEST_SUITE_REGISTRATION(DownloadHelperTest); + +void DownloadHelperTest::testCreateRequestGroupForUri() +{ + std::string array[] = { + "http://alpha/file", + "http://bravo/file", + "http://charlie/file" + }; + std::deque uris(&array[0], &array[arrayLength(array)]); + Option op; + op.put(PREF_SPLIT, "3"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + CPPUNIT_ASSERT_EQUAL((size_t)1, result.size()); + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)3, uris.size()); + for(size_t i = 0; i < arrayLength(array); ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i]); + } + CPPUNIT_ASSERT_EQUAL((unsigned int)3, group->getNumConcurrentCommand()); + SharedHandle ctx = group->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), ctx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/file.out"), + ctx->getActualBasePath()); + } + op.put(PREF_SPLIT, "5"); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + CPPUNIT_ASSERT_EQUAL((size_t)1, result.size()); + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)5, uris.size()); + for(size_t i = 0; i < arrayLength(array); ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i]); + } + for(size_t i = 0; i < 5-arrayLength(array); ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i+arrayLength(array)]); + } + CPPUNIT_ASSERT_EQUAL((unsigned int)5, group->getNumConcurrentCommand()); + } + op.put(PREF_SPLIT, "2"); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + CPPUNIT_ASSERT_EQUAL((size_t)1, result.size()); + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)3, uris.size()); + for(size_t i = 0; i < arrayLength(array); ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i]); + } + CPPUNIT_ASSERT_EQUAL((unsigned int)2, group->getNumConcurrentCommand()); + } + op.put(PREF_FORCE_SEQUENTIAL, V_TRUE); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + CPPUNIT_ASSERT_EQUAL((size_t)3, result.size()); + + // for alpha server + SharedHandle alphaGroup = result[0]; + std::deque alphaURIs; + alphaGroup->getURIs(alphaURIs); + CPPUNIT_ASSERT_EQUAL((size_t)2, alphaURIs.size()); + for(size_t i = 0; i < 2; ++i) { + CPPUNIT_ASSERT_EQUAL(array[0], uris[0]); + } + CPPUNIT_ASSERT_EQUAL((unsigned int)2, + alphaGroup->getNumConcurrentCommand()); + SharedHandle alphaCtx = alphaGroup->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), alphaCtx->getDir()); + // See the value of PREF_OUT is not used as a file name. + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/index.html"), + alphaCtx->getActualBasePath()); + + + } +} + +void DownloadHelperTest::testCreateRequestGroupForUri_parameterized() +{ + std::string array[] = { + "http://{alpha, bravo}/file", + "http://charlie/file" + }; + std::deque uris(&array[0], &array[arrayLength(array)]); + Option op; + op.put(PREF_SPLIT, "3"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + op.put(PREF_PARAMETERIZED_URI, V_TRUE); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + CPPUNIT_ASSERT_EQUAL((size_t)1, result.size()); + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)3, uris.size()); + + CPPUNIT_ASSERT_EQUAL(std::string("http://alpha/file"), uris[0]); + CPPUNIT_ASSERT_EQUAL(std::string("http://bravo/file"), uris[1]); + CPPUNIT_ASSERT_EQUAL(std::string("http://charlie/file"), uris[2]); + + CPPUNIT_ASSERT_EQUAL((unsigned int)3, group->getNumConcurrentCommand()); + SharedHandle ctx = group->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), ctx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/file.out"), + ctx->getActualBasePath()); + } +} + +void DownloadHelperTest::testCreateRequestGroupForUri_BitTorrent() +{ + std::string array[] = { + "http://alpha/file", + "test.torrent", + "http://bravo/file", + "http://charlie/file" + }; + std::deque uris(&array[0], &array[arrayLength(array)]); + Option op; + op.put(PREF_SPLIT, "3"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + CPPUNIT_ASSERT_EQUAL((size_t)2, result.size()); + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)3, uris.size()); + + CPPUNIT_ASSERT_EQUAL(array[0], uris[0]); + CPPUNIT_ASSERT_EQUAL(array[2], uris[1]); + CPPUNIT_ASSERT_EQUAL(array[3], uris[2]); + + CPPUNIT_ASSERT_EQUAL((unsigned int)3, group->getNumConcurrentCommand()); + SharedHandle ctx = group->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), ctx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/file.out"), + ctx->getActualBasePath()); + + SharedHandle torrentGroup = result[1]; + std::deque auxURIs; + torrentGroup->getURIs(auxURIs); + CPPUNIT_ASSERT(auxURIs.empty()); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, + torrentGroup->getNumConcurrentCommand()); + SharedHandle btctx = torrentGroup->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), btctx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/aria2-test"), + btctx->getActualBasePath()); + } +} + +void DownloadHelperTest::testCreateRequestGroupForUri_Metalink() +{ + std::string array[] = { + "http://alpha/file", + "http://bravo/file", + "http://charlie/file", + "test.xml" + }; + std::deque uris(&array[0], &array[arrayLength(array)]); + Option op; + op.put(PREF_SPLIT, "3"); + op.put(PREF_METALINK_SERVERS, "2"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + { + std::deque > result; + + createRequestGroupForUri(result, &op, uris); + + // group1: http://alpha/file, ... + // group2-7: 6 file entry in Metalink and 1 torrent file download + CPPUNIT_ASSERT_EQUAL((size_t)7, result.size()); + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)3, uris.size()); + for(size_t i = 0; i < 3; ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i]); + } + CPPUNIT_ASSERT_EQUAL((unsigned int)3, group->getNumConcurrentCommand()); + SharedHandle ctx = group->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), ctx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/file.out"), + ctx->getActualBasePath()); + + SharedHandle aria2052Group = result[1]; + CPPUNIT_ASSERT_EQUAL((unsigned int)1, // because of maxconnections attribute + aria2052Group->getNumConcurrentCommand()); + SharedHandle aria2052Ctx = + aria2052Group->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), aria2052Ctx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/aria2-0.5.2.tar.bz2"), + aria2052Ctx->getActualBasePath()); + + SharedHandle aria2051Group = result[2]; + CPPUNIT_ASSERT_EQUAL((unsigned int)2, + aria2051Group->getNumConcurrentCommand()); + } +} + +void DownloadHelperTest::testCreateRequestGroupForUriList() +{ + Option op; + op.put(PREF_SPLIT, "3"); + op.put(PREF_INPUT_FILE, "input_uris.txt"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + + std::deque > result; + + createRequestGroupForUriList(result, &op); + + CPPUNIT_ASSERT_EQUAL((size_t)2, result.size()); + + SharedHandle fileGroup = result[0]; + std::deque fileURIs; + fileGroup->getURIs(fileURIs); + CPPUNIT_ASSERT_EQUAL(std::string("http://alpha/file"), fileURIs[0]); + CPPUNIT_ASSERT_EQUAL(std::string("http://bravo/file"), fileURIs[1]); + CPPUNIT_ASSERT_EQUAL(std::string("http://charlie/file"), fileURIs[2]); + CPPUNIT_ASSERT_EQUAL((unsigned int)3, fileGroup->getNumConcurrentCommand()); + SharedHandle fileCtx = fileGroup->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/mydownloads"), fileCtx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/mydownloads/myfile.out"), + fileCtx->getActualBasePath()); + + SharedHandle fileISOGroup = result[1]; + SharedHandle fileISOCtx = fileISOGroup->getDownloadContext(); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp"), fileISOCtx->getDir()); + CPPUNIT_ASSERT_EQUAL(std::string("/tmp/file.out"), + fileISOCtx->getActualBasePath()); +} + +void DownloadHelperTest::testCreateRequestGroupForBitTorrent() +{ + std::string array[] = { + "http://alpha/file", + "http://bravo/file", + "http://charlie/file" + }; + + std::deque auxURIs(&array[0], &array[arrayLength(array)]); + Option op; + op.put(PREF_SPLIT, "5"); + op.put(PREF_TORRENT_FILE, "test.torrent"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + { + std::deque > result; + + createRequestGroupForBitTorrent(result, &op, auxURIs); + + CPPUNIT_ASSERT_EQUAL((size_t)1, result.size()); + + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + CPPUNIT_ASSERT_EQUAL((size_t)5, uris.size()); + for(size_t i = 0; i < arrayLength(array); ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i]); + } + for(size_t i = 0; i < 5-arrayLength(array); ++i) { + CPPUNIT_ASSERT_EQUAL(array[i], uris[i+arrayLength(array)]); + } + CPPUNIT_ASSERT_EQUAL((unsigned int)5, group->getNumConcurrentCommand()); + } + op.put(PREF_FORCE_SEQUENTIAL, V_TRUE); + { + std::deque > result; + + createRequestGroupForBitTorrent(result, &op, auxURIs); + + // See --force-requencial is ignored + CPPUNIT_ASSERT_EQUAL((size_t)1, result.size()); + } +} + +void DownloadHelperTest::testCreateRequestGroupForMetalink() +{ + Option op; + op.put(PREF_SPLIT, "3"); + op.put(PREF_METALINK_FILE, "test.xml"); + op.put(PREF_METALINK_SERVERS, "5"); + op.put(PREF_DIR, "/tmp"); + op.put(PREF_OUT, "file.out"); + { + std::deque > result; + + createRequestGroupForMetalink(result, &op); + + CPPUNIT_ASSERT_EQUAL((size_t)6, result.size()); + + SharedHandle group = result[0]; + std::deque uris; + group->getURIs(uris); + std::sort(uris.begin(), uris.end()); + CPPUNIT_ASSERT_EQUAL((size_t)2, uris.size()); + CPPUNIT_ASSERT_EQUAL(std::string("ftp://ftphost/aria2-0.5.2.tar.bz2"), + uris[0]); + CPPUNIT_ASSERT_EQUAL(std::string("http://httphost/aria2-0.5.2.tar.bz2"), + uris[1]); + // See numConcurrentCommand is 1 because of maxconnections attribute. + CPPUNIT_ASSERT_EQUAL((unsigned int)1, group->getNumConcurrentCommand()); + } +} + +} // namespace aria2 diff --git a/test/Makefile.am b/test/Makefile.am index 528fff93..dc1c7c9b 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -62,7 +62,8 @@ aria2c_SOURCES = AllTest.cc\ CopyDiskAdaptorTest.cc\ FtpConnectionTest.cc\ OptionParserTest.cc\ - SimpleDNSCacheTest.cc + SimpleDNSCacheTest.cc\ + DownloadHelperTest.cc if HAVE_LIBZ aria2c_SOURCES += GZipDecoderTest.cc @@ -185,7 +186,7 @@ endif # ENABLE_METALINK #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64 #aria2c_LDFLAGS = ${CPPUNIT_LIBS} -aria2c_LDADD = ../src/libaria2c.a\ +aria2c_LDADD = ../src/libaria2c.a ../src/download_helper.o\ @LIBINTL@ @LIBGNUTLS_LIBS@\ @LIBGCRYPT_LIBS@ @OPENSSL_LIBS@ @XML_LIBS@ @LIBARES_LIBS@\ @LIBCARES_LIBS@ @WINSOCK_LIBS@ @LIBEXPAT_LIBS@ @LIBZ_LIBS@\ diff --git a/test/Makefile.in b/test/Makefile.in index a49db8bc..3cfa8781 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -196,7 +196,8 @@ am__aria2c_SOURCES_DIST = AllTest.cc TestUtil.cc TestUtil.h \ ServerStatTest.cc NsCookieParserTest.cc \ DirectDiskAdaptorTest.cc CookieTest.cc CookieStorageTest.cc \ TimeTest.cc CopyDiskAdaptorTest.cc FtpConnectionTest.cc \ - OptionParserTest.cc SimpleDNSCacheTest.cc GZipDecoderTest.cc \ + OptionParserTest.cc SimpleDNSCacheTest.cc \ + DownloadHelperTest.cc GZipDecoderTest.cc \ Sqlite3MozCookieParserTest.cc MessageDigestHelperTest.cc \ IteratableChunkChecksumValidatorTest.cc \ IteratableChecksumValidatorTest.cc BtAllowedFastMessageTest.cc \ @@ -369,11 +370,13 @@ am_aria2c_OBJECTS = AllTest.$(OBJEXT) TestUtil.$(OBJEXT) \ CookieTest.$(OBJEXT) CookieStorageTest.$(OBJEXT) \ TimeTest.$(OBJEXT) CopyDiskAdaptorTest.$(OBJEXT) \ FtpConnectionTest.$(OBJEXT) OptionParserTest.$(OBJEXT) \ - SimpleDNSCacheTest.$(OBJEXT) $(am__objects_1) $(am__objects_2) \ - $(am__objects_3) $(am__objects_4) $(am__objects_5) + SimpleDNSCacheTest.$(OBJEXT) DownloadHelperTest.$(OBJEXT) \ + $(am__objects_1) $(am__objects_2) $(am__objects_3) \ + $(am__objects_4) $(am__objects_5) aria2c_OBJECTS = $(am_aria2c_OBJECTS) am__DEPENDENCIES_1 = -aria2c_DEPENDENCIES = ../src/libaria2c.a $(am__DEPENDENCIES_1) +aria2c_DEPENDENCIES = ../src/libaria2c.a ../src/download_helper.o \ + $(am__DEPENDENCIES_1) DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/depcomp am__depfiles_maybe = depfiles @@ -592,13 +595,13 @@ aria2c_SOURCES = AllTest.cc TestUtil.cc TestUtil.h SocketCoreTest.cc \ ServerStatTest.cc NsCookieParserTest.cc \ DirectDiskAdaptorTest.cc CookieTest.cc CookieStorageTest.cc \ TimeTest.cc CopyDiskAdaptorTest.cc FtpConnectionTest.cc \ - OptionParserTest.cc SimpleDNSCacheTest.cc $(am__append_1) \ - $(am__append_2) $(am__append_3) $(am__append_4) \ - $(am__append_5) + OptionParserTest.cc SimpleDNSCacheTest.cc \ + DownloadHelperTest.cc $(am__append_1) $(am__append_2) \ + $(am__append_3) $(am__append_4) $(am__append_5) #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64 #aria2c_LDFLAGS = ${CPPUNIT_LIBS} -aria2c_LDADD = ../src/libaria2c.a\ +aria2c_LDADD = ../src/libaria2c.a ../src/download_helper.o\ @LIBINTL@ @LIBGNUTLS_LIBS@\ @LIBGCRYPT_LIBS@ @OPENSSL_LIBS@ @XML_LIBS@ @LIBARES_LIBS@\ @LIBCARES_LIBS@ @WINSOCK_LIBS@ @LIBEXPAT_LIBS@ @LIBZ_LIBS@\ @@ -758,6 +761,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DictionaryTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DirectDiskAdaptorTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DownloadHandlerFactoryTest.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/DownloadHelperTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ExceptionTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FeatureConfigTest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FileEntryTest.Po@am__quote@ diff --git a/test/input_uris.txt b/test/input_uris.txt new file mode 100644 index 00000000..194383e8 --- /dev/null +++ b/test/input_uris.txt @@ -0,0 +1,5 @@ +http://alpha/file http://bravo/file http://charlie/file + out=myfile.out + dir=/mydownloads +http://delta/file.iso +