Ver Fonte

2010-03-06 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

	Added changeUri XML-RPC method.  This method removes/adds URIs
	dynamically.
	* doc/aria2c.1.txt
	* src/AbstractCommand.cc
	* src/DownloadContext.cc
	* src/DownloadContext.h
	* src/FileEntry.cc
	* src/FileEntry.h
	* src/Request.cc
	* src/Request.h
	* src/RequestGroup.cc
	* src/RequestGroupMan.cc
	* src/XmlRpcMethodFactory.cc
	* src/XmlRpcMethodImpl.cc
	* src/XmlRpcMethodImpl.h
	* test/FileEntryTest.cc
	* test/XmlRpcMethodTest.cc
Tatsuhiro Tsujikawa há 15 anos atrás
pai
commit
b1713e6373

+ 20 - 0
ChangeLog

@@ -1,3 +1,23 @@
+2010-03-06  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added changeUri XML-RPC method.  This method removes/adds URIs
+	dynamically.
+	* doc/aria2c.1.txt
+	* src/AbstractCommand.cc
+	* src/DownloadContext.cc
+	* src/DownloadContext.h
+	* src/FileEntry.cc
+	* src/FileEntry.h
+	* src/Request.cc
+	* src/Request.h
+	* src/RequestGroup.cc
+	* src/RequestGroupMan.cc
+	* src/XmlRpcMethodFactory.cc
+	* src/XmlRpcMethodImpl.cc
+	* src/XmlRpcMethodImpl.h
+	* test/FileEntryTest.cc
+	* test/XmlRpcMethodTest.cc
+
 2010-03-06  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 2010-03-06  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 
 	Rewritten copy ctor of RequestSlot to use initialization list.
 	Rewritten copy ctor of RequestSlot to use initialization list.

+ 6 - 2
doc/aria2c.1

@@ -2,12 +2,12 @@
 .\"     Title: aria2c
 .\"     Title: aria2c
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\"    Author: [FIXME: author] [see http://docbook.sf.net/el/author]
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
 .\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
-.\"      Date: 02/25/2010
+.\"      Date: 03/06/2010
 .\"    Manual: Aria2 Manual
 .\"    Manual: Aria2 Manual
 .\"    Source: Aria2 1.9.0a
 .\"    Source: Aria2 1.9.0a
 .\"  Language: English
 .\"  Language: English
 .\"
 .\"
-.TH "ARIA2C" "1" "02/25/2010" "Aria2 1\&.9\&.0a" "Aria2 Manual"
+.TH "ARIA2C" "1" "03/06/2010" "Aria2 1\&.9\&.0a" "Aria2 Manual"
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
 .\" * Define some portability stuff
 .\" * Define some portability stuff
 .\" -----------------------------------------------------------------
 .\" -----------------------------------------------------------------
@@ -2467,6 +2467,10 @@ This method changes the position of the download denoted by \fIgid\fR\&. \fIpos\
 .sp
 .sp
 For example, if GID#1 is placed in position 3, aria2\&.changePosition(1, \-1, POS_CUR) will change its position to 2\&. Additional aria2\&.changePosition(1, 0, POS_SET) will change its position to 0(the beginning of the queue)\&.
 For example, if GID#1 is placed in position 3, aria2\&.changePosition(1, \-1, POS_CUR) will change its position to 2\&. Additional aria2\&.changePosition(1, 0, POS_SET) will change its position to 0(the beginning of the queue)\&.
 .sp
 .sp
+\fBaria2\&.changeUri\fR \fIgid, fileIndex, delUris, addUris[, position]\fR
+.sp
+This method removes URIs in \fIdelUris\fR from and appends URIs in \fIaddUris\fR to download denoted by \fIgid\fR\&. \fIdelUris\fR and \fIaddUris\fR are list of string\&. A download can contain multiple files and URIs are attached to each file\&. \fIfileIndex\fR is used to select which file to remove/attach given URIs\&. \fIposition\fR is used to specify where URIs are inserted in the existing waiting URI list\&. \fIposition\fR is 0\-based\&. When \fIposition\fR is omitted, URIs are appended to the back of the list\&. This method first execute removal and then addition\&. \fIposition\fR is the position after URIs are removed, not the position when this method is called\&. When removing URI, if same URIs exist in download, only one of them is removed for each URI in \fIdelUris\fR\&. In other words, there are three URIs "http://example\&.org/aria2" and you want remove them all, you have to specify (at least) 3 "http://example\&.org/aria2" in \fIdelUris\fR\&. This method returns a list which contains 2 integers\&. The first integer is the number of URIs deleted\&. The second integer is the number of URIs added\&.
+.sp
 \fBaria2\&.getOption\fR \fIgid\fR
 \fBaria2\&.getOption\fR \fIgid\fR
 .sp
 .sp
 This method returns options of the download denoted by \fIgid\fR\&. The response is of type struct\&. Its key is the name of option\&. The value type is string\&.
 This method returns options of the download denoted by \fIgid\fR\&. The response is of type struct\&. Its key is the name of option\&. The value type is string\&.

+ 19 - 1
doc/aria2c.1.html

@@ -3176,6 +3176,24 @@ destination position.</p></div>
 -1, POS_CUR) will change its position to 2. Additional
 -1, POS_CUR) will change its position to 2. Additional
 aria2.changePosition(1, 0, POS_SET) will change its position to 0(the
 aria2.changePosition(1, 0, POS_SET) will change its position to 0(the
 beginning of the queue).</p></div>
 beginning of the queue).</p></div>
+<div class="paragraph"><p><strong>aria2.changeUri</strong> <em>gid, fileIndex, delUris, addUris[, position]</em></p></div>
+<div class="paragraph"><p>This method removes URIs in <em>delUris</em> from and appends URIs in
+<em>addUris</em> to download denoted by <em>gid</em>. <em>delUris</em> and <em>addUris</em> are
+list of string. A download can contain multiple files and URIs are
+attached to each file.  <em>fileIndex</em> is used to select which file to
+remove/attach given URIs.  <em>position</em> is used to specify where URIs
+are inserted in the existing waiting URI list. <em>position</em> is
+0-based. When <em>position</em> is omitted, URIs are appended to the back of
+the list.  This method first execute removal and then
+addition. <em>position</em> is the position after URIs are removed, not the
+position when this method is called.  When removing URI, if same URIs
+exist in download, only one of them is removed for each URI in
+<em>delUris</em>. In other words, there are three URIs
+"http://example.org/aria2" and you want remove them all, you have to
+specify (at least) 3 "http://example.org/aria2" in <em>delUris</em>.  This
+method returns a list which contains 2 integers. The first integer is
+the number of URIs deleted. The second integer is the number of URIs
+added.</p></div>
 <div class="paragraph"><p><strong>aria2.getOption</strong> <em>gid</em></p></div>
 <div class="paragraph"><p><strong>aria2.getOption</strong> <em>gid</em></p></div>
 <div class="paragraph"><p>This method returns options of the download denoted by <em>gid</em>.  The
 <div class="paragraph"><p>This method returns options of the download denoted by <em>gid</em>.  The
 response is of type struct. Its key is the name of option.  The value type
 response is of type struct. Its key is the name of option.  The value type
@@ -3722,7 +3740,7 @@ files in the program, then also delete it here.</p></div>
 <div id="footnotes"><hr /></div>
 <div id="footnotes"><hr /></div>
 <div id="footer">
 <div id="footer">
 <div id="footer-text">
 <div id="footer-text">
-Last updated 2010-02-23 23:01:01 JST
+Last updated 2010-03-06 23:18:27 JST
 </div>
 </div>
 </div>
 </div>
 </body>
 </body>

+ 20 - 0
doc/aria2c.1.txt

@@ -1465,6 +1465,26 @@ For example, if GID#1 is placed in position 3, aria2.changePosition(1,
 aria2.changePosition(1, 0, POS_SET) will change its position to 0(the
 aria2.changePosition(1, 0, POS_SET) will change its position to 0(the
 beginning of the queue).
 beginning of the queue).
 
 
+*aria2.changeUri* 'gid, fileIndex, delUris, addUris[, position]'
+
+This method removes URIs in 'delUris' from and appends URIs in
+'addUris' to download denoted by 'gid'. 'delUris' and 'addUris' are
+list of string. A download can contain multiple files and URIs are
+attached to each file.  'fileIndex' is used to select which file to
+remove/attach given URIs.  'position' is used to specify where URIs
+are inserted in the existing waiting URI list. 'position' is
+0-based. When 'position' is omitted, URIs are appended to the back of
+the list.  This method first execute removal and then
+addition. 'position' is the position after URIs are removed, not the
+position when this method is called.  When removing URI, if same URIs
+exist in download, only one of them is removed for each URI in
+'delUris'. In other words, there are three URIs
+"http://example.org/aria2" and you want remove them all, you have to
+specify (at least) 3 "http://example.org/aria2" in 'delUris'.  This
+method returns a list which contains 2 integers. The first integer is
+the number of URIs deleted. The second integer is the number of URIs
+added.
+
 *aria2.getOption* 'gid'
 *aria2.getOption* 'gid'
 
 
 This method returns options of the download denoted by 'gid'.  The
 This method returns options of the download denoted by 'gid'.  The

+ 8 - 0
src/AbstractCommand.cc

@@ -107,6 +107,14 @@ bool AbstractCommand::execute() {
       //logger->debug("CUID#%d - finished.", cuid);
       //logger->debug("CUID#%d - finished.", cuid);
       return true;
       return true;
     }
     }
+    if(!req.isNull() && req->removalRequested()) {
+      if(logger->debug()) {
+        logger->debug
+          ("CUID#%d - Discard original URI=%s because it is requested.",
+           cuid, req->getUrl().c_str());
+      }
+      return prepareForRetry(0);
+    }
     // TODO it is not needed to check other PeerStats every time.
     // TODO it is not needed to check other PeerStats every time.
     // Find faster Request when no segment is available.
     // Find faster Request when no segment is available.
     if(!req.isNull() && _fileEntry->countPooledRequest() > 0 &&
     if(!req.isNull() && _fileEntry->countPooledRequest() > 0 &&

+ 4 - 3
src/DownloadContext.cc

@@ -39,6 +39,7 @@
 #include "FileEntry.h"
 #include "FileEntry.h"
 #include "StringFormat.h"
 #include "StringFormat.h"
 #include "util.h"
 #include "util.h"
+#include "wallclock.h"
 
 
 namespace aria2 {
 namespace aria2 {
 
 
@@ -58,7 +59,7 @@ DownloadContext::DownloadContext(size_t pieceLength,
   _knowsTotalLength(true),
   _knowsTotalLength(true),
   _ownerRequestGroup(0),
   _ownerRequestGroup(0),
   _downloadStartTime(0),
   _downloadStartTime(0),
-  _downloadStopTime(_downloadStartTime)
+  _downloadStopTime(0)
 {
 {
   SharedHandle<FileEntry> fileEntry
   SharedHandle<FileEntry> fileEntry
     (new FileEntry(util::escapePath(path), totalLength, 0));
     (new FileEntry(util::escapePath(path), totalLength, 0));
@@ -67,12 +68,12 @@ DownloadContext::DownloadContext(size_t pieceLength,
 
 
 void DownloadContext::resetDownloadStartTime()
 void DownloadContext::resetDownloadStartTime()
 {
 {
-  _downloadStartTime.reset();
+  _downloadStartTime = global::wallclock;
 }
 }
 
 
 void DownloadContext::resetDownloadStopTime()
 void DownloadContext::resetDownloadStopTime()
 {
 {
-  _downloadStopTime.reset();
+  _downloadStopTime = global::wallclock;
 }
 }
 
 
 int64_t DownloadContext::calculateSessionTime() const
 int64_t DownloadContext::calculateSessionTime() const

+ 5 - 0
src/DownloadContext.h

@@ -233,6 +233,11 @@ public:
 
 
   void resetDownloadStopTime();
   void resetDownloadStopTime();
 
 
+  const Time& getDownloadStopTime() const
+  {
+    return _downloadStopTime;
+  }
+
   int64_t calculateSessionTime() const;
   int64_t calculateSessionTime() const;
   
   
   // Returns FileEntry at given offset. SharedHandle<FileEntry>() is
   // Returns FileEntry at given offset. SharedHandle<FileEntry>() is

+ 48 - 1
src/FileEntry.cc

@@ -218,7 +218,9 @@ void FileEntry::storePool(const SharedHandle<Request>& request)
 void FileEntry::poolRequest(const SharedHandle<Request>& request)
 void FileEntry::poolRequest(const SharedHandle<Request>& request)
 {
 {
   removeRequest(request);
   removeRequest(request);
-  storePool(request);
+  if(!request->removalRequested()) {
+    storePool(request);
+  }
 }
 }
 
 
 bool FileEntry::removeRequest(const SharedHandle<Request>& request)
 bool FileEntry::removeRequest(const SharedHandle<Request>& request)
@@ -330,4 +332,49 @@ void FileEntry::releaseRuntimeResource()
   _uriResults.clear();
   _uriResults.clear();
 }
 }
 
 
+template<typename InputIterator, typename T>
+static InputIterator findRequestByUri
+(InputIterator first, InputIterator last, const T& uri)
+{
+  for(; first != last; ++first) {
+    if(!(*first)->removalRequested() && (*first)->getUrl() == uri) {
+      return first;
+    }
+  }
+  return last;
+}
+
+bool FileEntry::removeUri(const std::string& uri)
+{
+  std::deque<std::string>::iterator itr =
+    std::find(_spentUris.begin(), _spentUris.end(), uri);
+  if(itr == _spentUris.end()) {
+    itr = std::find(_uris.begin(), _uris.end(), uri);
+    if(itr == _uris.end()) {
+      return false;
+    } else {
+      _uris.erase(itr);
+      return true;
+    }
+  } else {
+    _spentUris.erase(itr);
+    SharedHandle<Request> req;
+    std::deque<SharedHandle<Request> >::iterator riter =
+      findRequestByUri(_inFlightRequests.begin(), _inFlightRequests.end(), uri);
+    if(riter == _inFlightRequests.end()) {
+      riter = findRequestByUri(_requestPool.begin(), _requestPool.end(), uri);
+      if(riter == _requestPool.end()) {
+        return true;
+      } else {
+        req = *riter;
+        _requestPool.erase(riter);
+      }
+    } else {
+      req = *riter;
+    }
+    req->requestRemoval();
+    return true;
+  }
+}
+
 } // namespace aria2
 } // namespace aria2

+ 34 - 4
src/FileEntry.h

@@ -129,15 +129,43 @@ public:
     return _spentUris;
     return _spentUris;
   }
   }
 
 
-  void setUris(const std::vector<std::string>& uris)
+  size_t setUris(const std::vector<std::string>& uris)
   {
   {
-    _uris = std::deque<std::string>(uris.begin(), uris.end());
+    _uris.clear();
+    return addUris(uris.begin(), uris.end());
   }
   }
 
 
   template<typename InputIterator>
   template<typename InputIterator>
-  void addUris(InputIterator first, InputIterator last)
+  size_t addUris(InputIterator first, InputIterator last)
   {
   {
-    _uris.insert(_uris.end(), first, last);
+    size_t count = 0;
+    for(; first != last; ++first) {
+      if(addUri(*first)) {
+        ++count;
+      }
+    }
+    return count;
+  }
+
+  bool addUri(const std::string& uri)
+  {
+    if(Request().setUrl(uri)) {
+      _uris.push_back(uri);
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  bool insertUri(const std::string& uri, size_t pos)
+  {
+    if(Request().setUrl(uri)) {
+      pos = std::min(pos, _uris.size());
+      _uris.insert(_uris.begin()+pos, uri);
+      return true;
+    } else {
+      return false;
+    }
   }
   }
 
 
   // Inserts _uris and _spentUris into uris.
   // Inserts _uris and _spentUris into uris.
@@ -234,6 +262,8 @@ public:
   {
   {
     return _originalName;
     return _originalName;
   }
   }
+
+  bool removeUri(const std::string& uri);
 };
 };
 
 
 // Returns the first FileEntry which isRequested() method returns
 // Returns the first FileEntry which isRequested() method returns

+ 2 - 1
src/Request.cc

@@ -63,7 +63,8 @@ Request::Request():
   _maxPipelinedRequest(1),
   _maxPipelinedRequest(1),
   _method(METHOD_GET),
   _method(METHOD_GET),
   _hasPassword(false),
   _hasPassword(false),
-  _ipv6LiteralAddress(false)
+  _ipv6LiteralAddress(false),
+  _removalRequested(false)
 {}
 {}
 
 
 static std::string removeFragment(const std::string& url)
 static std::string removeFragment(const std::string& url)

+ 12 - 0
src/Request.h

@@ -88,6 +88,8 @@ private:
 
 
   SharedHandle<PeerStat> _peerStat;
   SharedHandle<PeerStat> _peerStat;
 
 
+  bool _removalRequested;
+
   bool parseUrl(const std::string& url);
   bool parseUrl(const std::string& url);
 public:
 public:
   Request();
   Request();
@@ -204,6 +206,16 @@ public:
 
 
   const SharedHandle<PeerStat>& initPeerStat();
   const SharedHandle<PeerStat>& initPeerStat();
 
 
+  void requestRemoval()
+  {
+    _removalRequested = true;
+  }
+
+  bool removalRequested() const
+  {
+    return _removalRequested;
+  }
+
   static const std::string METHOD_GET;
   static const std::string METHOD_GET;
   static const std::string METHOD_HEAD;
   static const std::string METHOD_HEAD;
 
 

+ 3 - 0
src/RequestGroup.cc

@@ -991,6 +991,9 @@ bool RequestGroup::needsFileAllocation() const
 
 
 DownloadResultHandle RequestGroup::createDownloadResult() const
 DownloadResultHandle RequestGroup::createDownloadResult() const
 {
 {
+  if(_logger->debug()) {
+    _logger->debug("GID#%d - Creating DownloadResult.", _gid);
+  }
   uint64_t sessionDownloadLength = 0;
   uint64_t sessionDownloadLength = 0;
 
 
 #ifdef ENABLE_BITTORRENT
 #ifdef ENABLE_BITTORRENT

+ 10 - 0
src/RequestGroupMan.cc

@@ -336,6 +336,16 @@ public:
   void operator()(const SharedHandle<RequestGroup>& group)
   void operator()(const SharedHandle<RequestGroup>& group)
   {
   {
     if(group->getNumCommand() == 0) {
     if(group->getNumCommand() == 0) {
+      SharedHandle<DownloadContext> dctx = group->getDownloadContext();
+      // DownloadContext::resetDownloadStopTime() is only called when
+      // download completed. If
+      // DownloadContext::getDownloadStopTime().isZero() is true, then
+      // there is a possibility that the download is error or
+      // in-progress and resetDownloadStopTime() is not called. So
+      // call it here.
+      if(dctx->getDownloadStopTime().isZero()) {
+        dctx->resetDownloadStopTime();
+      }
       try {
       try {
         group->closeFile();
         group->closeFile();
         if(group->downloadFinished()) {
         if(group->downloadFinished()) {

+ 2 - 0
src/XmlRpcMethodFactory.cc

@@ -79,6 +79,8 @@ XmlRpcMethodFactory::create(const std::string& methodName)
     return SharedHandle<XmlRpcMethod>(new TellStoppedXmlRpcMethod());
     return SharedHandle<XmlRpcMethod>(new TellStoppedXmlRpcMethod());
   } else if(methodName == GetOptionXmlRpcMethod::getMethodName()) {
   } else if(methodName == GetOptionXmlRpcMethod::getMethodName()) {
     return SharedHandle<XmlRpcMethod>(new GetOptionXmlRpcMethod());
     return SharedHandle<XmlRpcMethod>(new GetOptionXmlRpcMethod());
+  } else if(methodName == ChangeUriXmlRpcMethod::getMethodName()) {
+    return SharedHandle<XmlRpcMethod>(new ChangeUriXmlRpcMethod());
   } else if(methodName == ChangeOptionXmlRpcMethod::getMethodName()) {
   } else if(methodName == ChangeOptionXmlRpcMethod::getMethodName()) {
     return SharedHandle<XmlRpcMethod>(new ChangeOptionXmlRpcMethod());
     return SharedHandle<XmlRpcMethod>(new ChangeOptionXmlRpcMethod());
   } else if(methodName == GetGlobalOptionXmlRpcMethod::getMethodName()) {
   } else if(methodName == GetGlobalOptionXmlRpcMethod::getMethodName()) {

+ 61 - 0
src/XmlRpcMethodImpl.cc

@@ -851,6 +851,67 @@ BDE GetSessionInfoXmlRpcMethod::process
   return result;
   return result;
 }
 }
 
 
+BDE ChangeUriXmlRpcMethod::process
+(const XmlRpcRequest& req, DownloadEngine* e)
+{
+  const BDE& params = req._params;
+  assert(params.isList());
+
+  if(params.size() < 4 ||
+     !params[0].isString() || !params[1].isInteger() ||
+     !params[2].isList() || !params[3].isList() ||
+     params[1].i() < 0) {
+    throw DL_ABORT_EX("Bad request");
+  }
+  size_t pos = 0;
+  bool posGiven = false;
+  getPosParam(params, 4, posGiven, pos);
+
+  int32_t gid = util::parseInt(params[0].s());
+  size_t index = params[1].i();
+  const BDE& deluris = params[2];
+  const BDE& adduris = params[3];
+  SharedHandle<RequestGroup> group = findRequestGroup(e->_requestGroupMan, gid);
+  if(group.isNull()) {
+    throw DL_ABORT_EX
+      (StringFormat("Cannot remove URIs from GID#%d", gid).str());
+  }
+  SharedHandle<DownloadContext> dctx = group->getDownloadContext();
+  const std::vector<SharedHandle<FileEntry> >& files = dctx->getFileEntries();
+  if(files.size() <= index) {
+    throw DL_ABORT_EX(StringFormat("fileIndex is out of range").str());
+  }
+  SharedHandle<FileEntry> s = files[index];
+  size_t delcount = 0;
+  for(BDE::List::const_iterator i = deluris.listBegin(),
+        eoi = deluris.listEnd(); i != eoi; ++i) {
+    if(s->removeUri((*i).s())) {
+      ++delcount;
+    }
+  }
+  size_t addcount = 0;
+  if(posGiven) {
+    for(BDE::List::const_iterator i = adduris.listBegin(),
+          eoi = adduris.listEnd(); i != eoi; ++i) {
+      if(s->insertUri((*i).s(), pos)) {
+        ++addcount;
+        ++pos;
+      }
+    }
+  } else {
+    for(BDE::List::const_iterator i = adduris.listBegin(),
+          eoi = adduris.listEnd(); i != eoi; ++i) {
+      if(s->addUri((*i).s())) {
+        ++addcount;
+      }
+    }
+  }
+  BDE res = BDE::list();
+  res << delcount;
+  res << addcount;
+  return res;
+}
+
 BDE SystemMulticallXmlRpcMethod::process
 BDE SystemMulticallXmlRpcMethod::process
 (const XmlRpcRequest& req, DownloadEngine* e)
 (const XmlRpcRequest& req, DownloadEngine* e)
 {
 {

+ 11 - 0
src/XmlRpcMethodImpl.h

@@ -341,6 +341,17 @@ public:
   }
   }
 };
 };
 
 
+class ChangeUriXmlRpcMethod:public XmlRpcMethod {
+protected:
+  virtual BDE process(const XmlRpcRequest& req, DownloadEngine* e);
+public:
+  static const std::string& getMethodName()
+  {
+    static std::string methodName = "aria2.changeUri";
+    return methodName;
+  }
+};
+
 class GetSessionInfoXmlRpcMethod:public XmlRpcMethod {
 class GetSessionInfoXmlRpcMethod:public XmlRpcMethod {
 protected:
 protected:
   virtual BDE process(const XmlRpcRequest& req, DownloadEngine* e);
   virtual BDE process(const XmlRpcRequest& req, DownloadEngine* e);

+ 67 - 0
test/FileEntryTest.cc

@@ -15,6 +15,10 @@ class FileEntryTest : public CppUnit::TestFixture {
   CPPUNIT_TEST(testGetRequest);
   CPPUNIT_TEST(testGetRequest);
   CPPUNIT_TEST(testGetRequest_disableSingleHostMultiConnection);
   CPPUNIT_TEST(testGetRequest_disableSingleHostMultiConnection);
   CPPUNIT_TEST(testReuseUri);
   CPPUNIT_TEST(testReuseUri);
+  CPPUNIT_TEST(testAddUri);
+  CPPUNIT_TEST(testAddUris);
+  CPPUNIT_TEST(testInsertUri);
+  CPPUNIT_TEST(testRemoveUri);
   CPPUNIT_TEST_SUITE_END();
   CPPUNIT_TEST_SUITE_END();
 public:
 public:
   void setUp() {}
   void setUp() {}
@@ -25,6 +29,10 @@ public:
   void testGetRequest();
   void testGetRequest();
   void testGetRequest_disableSingleHostMultiConnection();
   void testGetRequest_disableSingleHostMultiConnection();
   void testReuseUri();
   void testReuseUri();
+  void testAddUri();
+  void testAddUris();
+  void testInsertUri();
+  void testRemoveUri();
 };
 };
 
 
 
 
@@ -149,4 +157,63 @@ void FileEntryTest::testReuseUri()
   CPPUNIT_ASSERT_EQUAL(std::string("ftp://localhost/aria2.zip"), uris[2]);
   CPPUNIT_ASSERT_EQUAL(std::string("ftp://localhost/aria2.zip"), uris[2]);
 }
 }
 
 
+void FileEntryTest::testAddUri()
+{
+  FileEntry file;
+  CPPUNIT_ASSERT(file.addUri("http://good"));
+  CPPUNIT_ASSERT(!file.addUri("bad"));
+}
+
+void FileEntryTest::testAddUris()
+{
+  FileEntry file;
+  std::string uris[] = {"bad", "http://good"};
+  CPPUNIT_ASSERT_EQUAL((size_t)1, file.addUris(&uris[0], &uris[2]));
+}
+
+void FileEntryTest::testInsertUri()
+{
+  FileEntry file;
+  CPPUNIT_ASSERT(file.insertUri("http://example.org/1", 0));
+  CPPUNIT_ASSERT(file.insertUri("http://example.org/2", 0));
+  CPPUNIT_ASSERT(file.insertUri("http://example.org/3", 1));
+  CPPUNIT_ASSERT(file.insertUri("http://example.org/4", 5));
+  std::deque<std::string> uris = file.getRemainingUris();
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/2"), uris[0]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/3"), uris[1]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/1"), uris[2]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/4"), uris[3]);
+}
+
+void FileEntryTest::testRemoveUri()
+{
+  SharedHandle<InOrderURISelector> selector(new InOrderURISelector());
+  FileEntry file;
+  file.addUri("http://example.org/");
+  CPPUNIT_ASSERT(file.removeUri("http://example.org/"));
+  CPPUNIT_ASSERT(file.getRemainingUris().empty());
+  CPPUNIT_ASSERT(!file.removeUri("http://example.org/"));
+
+  file.addUri("http://example.org/");
+  SharedHandle<Request> exampleOrgReq = file.getRequest(selector);
+  CPPUNIT_ASSERT(!exampleOrgReq->removalRequested());
+  CPPUNIT_ASSERT_EQUAL((size_t)1, file.getSpentUris().size());
+  CPPUNIT_ASSERT(file.removeUri("http://example.org/"));
+  CPPUNIT_ASSERT(file.getSpentUris().empty());
+  CPPUNIT_ASSERT(exampleOrgReq->removalRequested());
+  file.poolRequest(exampleOrgReq);
+  CPPUNIT_ASSERT_EQUAL((size_t)0, file.countPooledRequest());
+
+  file.addUri("http://example.org/");
+  exampleOrgReq = file.getRequest(selector);
+  file.poolRequest(exampleOrgReq);
+  CPPUNIT_ASSERT_EQUAL((size_t)1, file.countPooledRequest());
+  CPPUNIT_ASSERT(file.removeUri("http://example.org/"));
+  CPPUNIT_ASSERT_EQUAL((size_t)0, file.countPooledRequest());
+  CPPUNIT_ASSERT(file.getSpentUris().empty());
+
+  file.addUri("http://example.org/");
+  CPPUNIT_ASSERT(!file.removeUri("http://example.net"));
+}
+
 } // namespace aria2
 } // namespace aria2

+ 129 - 0
test/XmlRpcMethodTest.cc

@@ -75,6 +75,8 @@ class XmlRpcMethodTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testChangePosition);
   CPPUNIT_TEST(testChangePosition);
   CPPUNIT_TEST(testChangePosition_fail);
   CPPUNIT_TEST(testChangePosition_fail);
   CPPUNIT_TEST(testGetSessionInfo);
   CPPUNIT_TEST(testGetSessionInfo);
+  CPPUNIT_TEST(testChangeUri);
+  CPPUNIT_TEST(testChangeUri_fail);
   CPPUNIT_TEST(testSystemMulticall);
   CPPUNIT_TEST(testSystemMulticall);
   CPPUNIT_TEST(testSystemMulticall_fail);
   CPPUNIT_TEST(testSystemMulticall_fail);
   CPPUNIT_TEST_SUITE_END();
   CPPUNIT_TEST_SUITE_END();
@@ -135,6 +137,8 @@ public:
   void testChangePosition();
   void testChangePosition();
   void testChangePosition_fail();
   void testChangePosition_fail();
   void testGetSessionInfo();
   void testGetSessionInfo();
+  void testChangeUri();
+  void testChangeUri_fail();
   void testSystemMulticall();
   void testSystemMulticall();
   void testSystemMulticall_fail();
   void testSystemMulticall_fail();
 };
 };
@@ -779,6 +783,131 @@ void XmlRpcMethodTest::testChangePosition_fail()
   CPPUNIT_ASSERT_EQUAL(1, res._code);
   CPPUNIT_ASSERT_EQUAL(1, res._code);
 }
 }
 
 
+void XmlRpcMethodTest::testChangeUri()
+{
+  SharedHandle<FileEntry> files[3];
+  for(int i = 0; i < 3; ++i) {
+    files[i].reset(new FileEntry());
+  }
+  files[1]->addUri("http://example.org/aria2.tar.bz2");
+  files[1]->addUri("http://example.org/mustremove1");
+  files[1]->addUri("http://example.org/mustremove2");
+  SharedHandle<DownloadContext> dctx(new DownloadContext());
+  dctx->setFileEntries(&files[0], &files[3]);
+  SharedHandle<RequestGroup> group(new RequestGroup(_option));
+  group->setDownloadContext(dctx);
+  _e->_requestGroupMan->addReservedGroup(group);
+
+  ChangeUriXmlRpcMethod m;
+  XmlRpcRequest req(ChangeUriXmlRpcMethod::getMethodName(), BDE::list());
+  req._params << std::string("1"); // GID
+  req._params << 1; // index of FileEntry
+  BDE removeuris = BDE::list();
+  removeuris << std::string("http://example.org/mustremove1");
+  removeuris << std::string("http://example.org/mustremove2");
+  removeuris << std::string("http://example.org/notexist");
+  req._params << removeuris;
+  BDE adduris = BDE::list();
+  adduris << std::string("http://example.org/added1");
+  adduris << std::string("http://example.org/added2");
+  adduris << std::string("baduri");
+  adduris << std::string("http://example.org/added3");
+  req._params << adduris;
+  XmlRpcResponse res = m.execute(req, _e.get());
+  CPPUNIT_ASSERT_EQUAL(0, res._code);
+  CPPUNIT_ASSERT_EQUAL((int64_t)2, res._param[0].i());
+  CPPUNIT_ASSERT_EQUAL((int64_t)3, res._param[1].i());
+  CPPUNIT_ASSERT_EQUAL((size_t)0, files[0]->getRemainingUris().size());
+  CPPUNIT_ASSERT_EQUAL((size_t)0, files[2]->getRemainingUris().size());
+  std::deque<std::string> uris = files[1]->getRemainingUris();
+  CPPUNIT_ASSERT_EQUAL((size_t)4, uris.size());
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/aria2.tar.bz2"),uris[0]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added1"), uris[1]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added2"), uris[2]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added3"), uris[3]);
+
+  // Change adduris
+  adduris = BDE::list();
+  adduris << std::string("http://example.org/added1-1");
+  adduris << std::string("http://example.org/added1-2");
+  req._params[3] = adduris;
+  // Set position parameter
+  req._params << 2;
+  res = m.execute(req, _e.get());
+  CPPUNIT_ASSERT_EQUAL(0, res._code);
+  CPPUNIT_ASSERT_EQUAL((int64_t)0, res._param[0].i());
+  CPPUNIT_ASSERT_EQUAL((int64_t)2, res._param[1].i());
+  uris = files[1]->getRemainingUris();
+  CPPUNIT_ASSERT_EQUAL((size_t)6, uris.size());
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added1-1"), uris[2]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added1-2"), uris[3]);
+
+  // Change index of FileEntry
+  req._params[1] = 0;
+  // Set position far beyond the size of uris in FileEntry.
+  req._params[4] = 1000;
+  res = m.execute(req, _e.get());
+  CPPUNIT_ASSERT_EQUAL(0, res._code);
+  CPPUNIT_ASSERT_EQUAL((int64_t)0, res._param[0].i());
+  CPPUNIT_ASSERT_EQUAL((int64_t)2, res._param[1].i());
+  uris = files[0]->getRemainingUris();
+  CPPUNIT_ASSERT_EQUAL((size_t)2, uris.size());
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added1-1"), uris[0]);
+  CPPUNIT_ASSERT_EQUAL(std::string("http://example.org/added1-2"), uris[1]);
+}
+
+void XmlRpcMethodTest::testChangeUri_fail()
+{
+  SharedHandle<FileEntry> files[3];
+  for(int i = 0; i < 3; ++i) {
+    files[i].reset(new FileEntry());
+  }
+  SharedHandle<DownloadContext> dctx(new DownloadContext());
+  dctx->setFileEntries(&files[0], &files[3]);
+  SharedHandle<RequestGroup> group(new RequestGroup(_option));
+  group->setDownloadContext(dctx);
+  _e->_requestGroupMan->addReservedGroup(group);
+
+  ChangeUriXmlRpcMethod m;
+  XmlRpcRequest req(ChangeUriXmlRpcMethod::getMethodName(), BDE::list());
+  req._params << std::string("1"); // GID
+  req._params << 0; // index of FileEntry
+  BDE removeuris = BDE::list();
+  req._params << removeuris;
+  BDE adduris = BDE::list();
+  req._params << adduris;
+  XmlRpcResponse res = m.execute(req, _e.get());
+  CPPUNIT_ASSERT_EQUAL(0, res._code);
+
+  req._params[0] = std::string("2");
+  res = m.execute(req, _e.get());  
+  // RPC request fails because GID#2 does not exist.
+  CPPUNIT_ASSERT_EQUAL(1, res._code);
+
+  req._params[0] = std::string("1");
+  req._params[1] = 3;
+  res = m.execute(req, _e.get());  
+  // RPC request fails because FileEntry#3 does not exist.
+  CPPUNIT_ASSERT_EQUAL(1, res._code);
+
+  req._params[1] = std::string("0");
+  res = m.execute(req, _e.get());  
+  // RPC request fails because index of FileEntry is string.
+  CPPUNIT_ASSERT_EQUAL(1, res._code);
+
+  req._params[1] = 0;
+  req._params[2] = std::string("http://url");
+  res = m.execute(req, _e.get());  
+  // RPC request fails because 3rd param is not list.
+  CPPUNIT_ASSERT_EQUAL(1, res._code);
+
+  req._params[2] = BDE::list();
+  req._params[3] = std::string("http://url");
+  res = m.execute(req, _e.get());  
+  // RPC request fails because 4th param is not list.
+  CPPUNIT_ASSERT_EQUAL(1, res._code);
+}
+
 void XmlRpcMethodTest::testGetSessionInfo()
 void XmlRpcMethodTest::testGetSessionInfo()
 {
 {
   GetSessionInfoXmlRpcMethod m;
   GetSessionInfoXmlRpcMethod m;