Prechádzať zdrojové kódy

2009-03-07 Tatsuhiro Tsujikawa <t-tujikawa@users.sourceforge.net>

	Added --index-out option to specify each file path for torrent.
	Here index shown in --show-files option is used to specify which
	file path should be altered. For example, to change the file
	path with index=2, use --index-out=2=aria2.tar.bz2. You can use
	this option multiple times: --index-out=1=aria2.tar.bz2
	--index-out=2=aria2-opt.tar.bz2. The short hand form -O is also
	available. This option can be specified in -i list.	
	* src/DefaultBtContext.cc
	* src/DefaultBtContext.h
	* src/MultiDiskAdaptor.cc
	* src/MultiDiskAdaptor.h
	* src/MultiFileAllocationIterator.cc
	* src/OptionHandlerFactory.cc
	* src/OptionHandlerImpl.h
	* src/Util.cc
	* src/Util.h
	* src/download_helper.cc
	* src/prefs.cc
	* src/prefs.h
	* src/usage_text.h
	* test/DefaultBtContextTest.cc
	* test/MultiDiskAdaptorTest.cc
	* test/MultiFileAllocationIteratorTest.cc
	* test/UtilTest.cc
Tatsuhiro Tsujikawa 16 rokov pred
rodič
commit
f44554a634

+ 27 - 0
ChangeLog

@@ -1,3 +1,30 @@
+2009-03-07  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
+
+	Added --index-out option to specify each file path for torrent.
+	Here index shown in --show-files option is used to specify which
+	file path should be altered. For example, to change the file path
+	with index=2, use --index-out=2=aria2.tar.bz2. You can use this
+	option multiple times: --index-out=1=aria2.tar.bz2
+	--index-out=2=aria2-opt.tar.bz2. The short hand form -O is also
+	available. This option can be specified in -i list.	
+	* src/DefaultBtContext.cc
+	* src/DefaultBtContext.h
+	* src/MultiDiskAdaptor.cc
+	* src/MultiDiskAdaptor.h
+	* src/MultiFileAllocationIterator.cc
+	* src/OptionHandlerFactory.cc
+	* src/OptionHandlerImpl.h
+	* src/Util.cc
+	* src/Util.h
+	* src/download_helper.cc
+	* src/prefs.cc
+	* src/prefs.h
+	* src/usage_text.h
+	* test/DefaultBtContextTest.cc
+	* test/MultiDiskAdaptorTest.cc
+	* test/MultiFileAllocationIteratorTest.cc
+	* test/UtilTest.cc
+
 2009-03-05  Tatsuhiro Tsujikawa  <t-tujikawa@users.sourceforge.net>
 
 	Use Util::pathJoin()

+ 21 - 4
src/DefaultBtContext.cc

@@ -151,14 +151,20 @@ void DefaultBtContext::extractFileEntries(const bencode::BDE& infoDict,
 	throw DlAbortEx("Path is empty.");
       }
       
-      std::vector<std::string> elements(pathList.size());
-      std::transform(pathList.listBegin(), pathList.listEnd(), elements.begin(),
+      std::vector<std::string> pathelem(pathList.size());
+      std::transform(pathList.listBegin(), pathList.listEnd(), pathelem.begin(),
 		     std::mem_fun_ref(&bencode::BDE::s));
-      std::string path = Util::joinPath(elements.begin(), elements.end());
+      std::string path = Util::joinPath(pathelem.begin(), pathelem.end());
+      // Split path with '/' again because each pathList element can
+      // contain "/" inside.
+      std::deque<std::string> elements;
+      Util::slice(elements, path, '/');
+      elements.push_front(name);
+      path = Util::joinPath(elements.begin(), elements.end());
 
       std::deque<std::string> uris;
       std::transform(urlList.begin(), urlList.end(), std::back_inserter(uris),
-		     std::bind2nd(std::plus<std::string>(), "/"+name+"/"+path));
+		     std::bind2nd(std::plus<std::string>(), "/"+path));
       FileEntryHandle fileEntry(new FileEntry(path, fileLengthData.i(),
 					      offset, uris));
       fileEntries.push_back(fileEntry);
@@ -475,4 +481,15 @@ void DefaultBtContext::setInfoHash(const unsigned char* infoHash)
   memcpy(this->infoHash, infoHash, sizeof(this->infoHash));
 }
 
+void DefaultBtContext::setFilePathWithIndex
+(size_t index, const std::string& path)
+{
+  if(0 < index && index <= fileEntries.size()) {
+    fileEntries[index-1]->setPath(path);
+  } else {
+    throw DlAbortEx(StringFormat("No such file with index=%u",
+				 static_cast<unsigned int>(index)).str());
+  }
+}
+
 } // namespace aria2

+ 6 - 0
src/DefaultBtContext.h

@@ -182,6 +182,12 @@ private:
 
   void setRandomizer(const SharedHandle<Randomizer>& randomizer);
 
+  // Sets file path for spcified index. index starts from 1. The index
+  // is the same used in BtContext::setFileFilter().  Please note that
+  // path is not the actual file path. The actual file path is
+  // getDir()+"/"+path.
+  void setFilePathWithIndex(size_t index, const std::string& path);
+
   friend std::ostream& operator<<(std::ostream& o, const DefaultBtContext& ctx);
 
   static const std::string DEFAULT_PEER_ID_PREFIX;

+ 38 - 35
src/MultiDiskAdaptor.cc

@@ -58,15 +58,15 @@ DiskWriterEntry::DiskWriterEntry(const SharedHandle<FileEntry>& fileEntry):
 
 DiskWriterEntry::~DiskWriterEntry() {}
 
-std::string DiskWriterEntry::getFilePath(const std::string& topDir) const
+std::string DiskWriterEntry::getFilePath(const std::string& storeDir) const
 {
-  return topDir+"/"+fileEntry->getPath();
+  return storeDir+"/"+fileEntry->getPath();
 }
 
-void DiskWriterEntry::initAndOpenFile(const std::string& topDir)
+void DiskWriterEntry::initAndOpenFile(const std::string& storeDir)
 {
   if(!diskWriter.isNull()) {
-    diskWriter->initAndOpenFile(getFilePath(topDir), fileEntry->getLength());
+    diskWriter->initAndOpenFile(getFilePath(storeDir), fileEntry->getLength());
     if(_directIO) {
       diskWriter->enableDirectIO();
     }
@@ -74,10 +74,10 @@ void DiskWriterEntry::initAndOpenFile(const std::string& topDir)
   }
 }
 
-void DiskWriterEntry::openFile(const std::string& topDir)
+void DiskWriterEntry::openFile(const std::string& storeDir)
 {
   if(!diskWriter.isNull()) {
-    diskWriter->openFile(getFilePath(topDir), fileEntry->getLength());
+    diskWriter->openFile(getFilePath(storeDir), fileEntry->getLength());
     if(_directIO) {
       diskWriter->enableDirectIO();
     }
@@ -85,10 +85,10 @@ void DiskWriterEntry::openFile(const std::string& topDir)
   }
 }
 
-void DiskWriterEntry::openExistingFile(const std::string& topDir)
+void DiskWriterEntry::openExistingFile(const std::string& storeDir)
 {
   if(!diskWriter.isNull()) {
-    diskWriter->openExistingFile(getFilePath(topDir), fileEntry->getLength());
+    diskWriter->openExistingFile(getFilePath(storeDir), fileEntry->getLength());
     if(_directIO) {
       diskWriter->enableDirectIO();
     }
@@ -109,9 +109,9 @@ void DiskWriterEntry::closeFile()
   }
 }
 
-bool DiskWriterEntry::fileExists(const std::string& topDir)
+bool DiskWriterEntry::fileExists(const std::string& storeDir)
 {
-  return File(getFilePath(topDir)).exists();
+  return File(getFilePath(storeDir)).exists();
 }
 
 uint64_t DiskWriterEntry::size() const
@@ -270,9 +270,9 @@ void MultiDiskAdaptor::resetDiskWriterEntries()
 	diskWriterEntries.begin(); i != diskWriterEntries.end(); ++i) {
     if((*i)->needsFileAllocation() ||
        dwreq.find((*i)->getFileEntry()->getPath()) != dwreq.end() ||
-       (*i)->fileExists(getTopDirPath())) {
+       (*i)->fileExists(storeDir)) {
       logger->debug("Creating DiskWriter for filename=%s",
-		    (*i)->getFilePath(getTopDirPath()).c_str());
+		    (*i)->getFilePath(storeDir).c_str());
       (*i)->setDiskWriter(dwFactory.newDiskWriter());
       (*i)->getDiskWriter()->setDirectIOAllowed(_directIOAllowed);
       if(_readOnly) {
@@ -287,25 +287,25 @@ std::string MultiDiskAdaptor::getTopDirPath() const
   return storeDir+"/"+topDir;
 }
 
-void MultiDiskAdaptor::mkdir(const std::string& topDirPath) const
+void MultiDiskAdaptor::mkdir(const std::string& storeDir) const
 {
   for(std::deque<SharedHandle<DiskWriterEntry> >::const_iterator i =
 	diskWriterEntries.begin(); i != diskWriterEntries.end(); ++i) {
-    (*i)->getFileEntry()->setupDir(topDirPath);
+    (*i)->getFileEntry()->setupDir(storeDir);
   }
 }
 
 void MultiDiskAdaptor::openIfNot
 (const SharedHandle<DiskWriterEntry>& entry,
  void (DiskWriterEntry::*open)(const std::string&),
- const std::string& topDirPath)
+ const std::string& storeDir)
 {
   if(!entry->isOpen()) {
 //     logger->debug("DiskWriterEntry: Cache MISS. offset=%s",
 // 		  Util::itos(entry->getFileEntry()->getOffset()).c_str());
  
     size_t numOpened = _openedDiskWriterEntries.size();
-    (entry.get()->*open)(topDirPath);
+    (entry.get()->*open)(storeDir);
     if(numOpened >= _maxOpenFiles) {
       // Cache is full. 
       // Choose one DiskWriterEntry randomly and close it.
@@ -326,32 +326,29 @@ void MultiDiskAdaptor::openIfNot
 
 void MultiDiskAdaptor::openFile()
 {
-  _cachedTopDirPath = getTopDirPath();
   resetDiskWriterEntries();
-  mkdir(_cachedTopDirPath);
+  mkdir(storeDir);
   // Call DiskWriterEntry::openFile to make sure that zero-length files are
   // created.
   for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
       itr != diskWriterEntries.end(); ++itr) {
-    openIfNot(*itr, &DiskWriterEntry::openFile, _cachedTopDirPath);
+    openIfNot(*itr, &DiskWriterEntry::openFile, storeDir);
   }
 }
 
 void MultiDiskAdaptor::initAndOpenFile()
 {
-  _cachedTopDirPath = getTopDirPath();
   resetDiskWriterEntries();
-  mkdir(_cachedTopDirPath);
+  mkdir(storeDir);
   // Call DiskWriterEntry::initAndOpenFile to make files truncated.
   for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
       itr != diskWriterEntries.end(); ++itr) {
-    openIfNot(*itr, &DiskWriterEntry::initAndOpenFile, _cachedTopDirPath);
+    openIfNot(*itr, &DiskWriterEntry::initAndOpenFile, storeDir);
   }
 }
 
 void MultiDiskAdaptor::openExistingFile()
 {
-  _cachedTopDirPath = getTopDirPath();
   resetDiskWriterEntries();
   // Not need to call openIfNot here.
 }
@@ -416,12 +413,12 @@ findFirstDiskWriterEntry(const DiskWriterEntries& diskWriterEntries, off_t offse
 
 static void throwOnDiskWriterNotOpened(const SharedHandle<DiskWriterEntry>& e,
 				       off_t offset,
-				       const std::string& topDirPath)
+				       const std::string& storeDir)
 {
   throw DlAbortEx
     (StringFormat("DiskWriter for offset=%s, filename=%s is not opened.",
 		  Util::itos(offset).c_str(),
-		  e->getFilePath(topDirPath).c_str()).str());  
+		  e->getFilePath(storeDir).c_str()).str());  
 }
 
 void MultiDiskAdaptor::writeData(const unsigned char* data, size_t len,
@@ -434,10 +431,10 @@ void MultiDiskAdaptor::writeData(const unsigned char* data, size_t len,
   for(DiskWriterEntries::const_iterator i = first; i != diskWriterEntries.end(); ++i) {
     size_t writeLength = calculateLength(*i, fileOffset, rem);
 
-    openIfNot(*i, &DiskWriterEntry::openFile, _cachedTopDirPath);
+    openIfNot(*i, &DiskWriterEntry::openFile, storeDir);
 
     if(!(*i)->isOpen()) {
-      throwOnDiskWriterNotOpened(*i, offset+(len-rem), _cachedTopDirPath);
+      throwOnDiskWriterNotOpened(*i, offset+(len-rem), storeDir);
     }
 
     (*i)->getDiskWriter()->writeData(data+(len-rem), writeLength, fileOffset);
@@ -459,10 +456,10 @@ ssize_t MultiDiskAdaptor::readData(unsigned char* data, size_t len, off_t offset
   for(DiskWriterEntries::const_iterator i = first; i != diskWriterEntries.end(); ++i) {
     size_t readLength = calculateLength(*i, fileOffset, rem);
 
-    openIfNot(*i, &DiskWriterEntry::openFile, _cachedTopDirPath);
+    openIfNot(*i, &DiskWriterEntry::openFile, storeDir);
 
     if(!(*i)->isOpen()) {
-      throwOnDiskWriterNotOpened(*i, offset+(len-rem), _cachedTopDirPath);
+      throwOnDiskWriterNotOpened(*i, offset+(len-rem), storeDir);
     }
 
     totalReadLength +=
@@ -478,12 +475,13 @@ ssize_t MultiDiskAdaptor::readData(unsigned char* data, size_t len, off_t offset
 
 bool MultiDiskAdaptor::fileExists()
 {
+  // TODO Use FileEntry::fileExists() here.
+
   // Don't use _cachedTopDirPath because they are initialized after opening files.
   // This method could be called before opening files.
-  std::string topDirPath = getTopDirPath();
   for(std::deque<SharedHandle<FileEntry> >::iterator i =
 	fileEntries.begin(); i != fileEntries.end(); ++i) {
-    if(File(topDirPath+"/"+(*i)->getPath()).exists()) {
+    if(File(storeDir+"/"+(*i)->getPath()).exists()) {
       return true;
     }
   }
@@ -496,7 +494,7 @@ uint64_t MultiDiskAdaptor::size()
   uint64_t size = 0;
   for(DiskWriterEntries::const_iterator itr = diskWriterEntries.begin();
       itr != diskWriterEntries.end(); ++itr) {
-    openIfNot(*itr, &DiskWriterEntry::openFile, _cachedTopDirPath);
+    openIfNot(*itr, &DiskWriterEntry::openFile, storeDir);
     size += (*itr)->size();
   }
   return size;
@@ -538,9 +536,9 @@ void MultiDiskAdaptor::cutTrailingGarbage()
   for(std::deque<SharedHandle<DiskWriterEntry> >::const_iterator i =
 	diskWriterEntries.begin(); i != diskWriterEntries.end(); ++i) {
     uint64_t length = (*i)->getFileEntry()->getLength();
-    if(File((*i)->getFilePath(_cachedTopDirPath)).size() > length) {
+    if(File((*i)->getFilePath(storeDir)).size() > length) {
       // We need open file before calling DiskWriter::truncate(uint64_t)
-      openIfNot(*i, &DiskWriterEntry::openFile, _cachedTopDirPath);
+      openIfNot(*i, &DiskWriterEntry::openFile, storeDir);
       (*i)->getDiskWriter()->truncate(length);
     }
   }
@@ -557,7 +555,7 @@ size_t MultiDiskAdaptor::utime(const Time& actime, const Time& modtime)
   for(std::deque<SharedHandle<FileEntry> >::const_iterator i =
 	fileEntries.begin(); i != fileEntries.end(); ++i) {
     if((*i)->isRequested()) {
-      File f(getTopDirPath()+"/"+(*i)->getPath());
+      File f(storeDir+"/"+(*i)->getPath());
       if(f.isFile() && f.utime(actime, modtime)) {
 	++numOK;
       }
@@ -572,4 +570,9 @@ MultiDiskAdaptor::getDiskWriterEntries() const
   return diskWriterEntries;
 }
 
+const std::string& MultiDiskAdaptor::getStoreDir() const
+{
+  return storeDir;
+}
+
 } // namespace aria2

+ 9 - 9
src/MultiDiskAdaptor.h

@@ -55,19 +55,19 @@ public:
 
   ~DiskWriterEntry();
 
-  std::string getFilePath(const std::string& topDir) const;
+  std::string getFilePath(const std::string& storeDir) const;
 
-  void initAndOpenFile(const std::string& topDir);
+  void initAndOpenFile(const std::string& storeDir);
 
-  void openFile(const std::string& topDir);
+  void openFile(const std::string& storeDir);
 
-  void openExistingFile(const std::string& topDir);
+  void openExistingFile(const std::string& storeDir);
 
   void closeFile();
 
   bool isOpen() const;
 
-  bool fileExists(const std::string& topDir);
+  bool fileExists(const std::string& storeDir);
 
   uint64_t size() const;
 
@@ -105,8 +105,6 @@ private:
   size_t pieceLength;
   DiskWriterEntries diskWriterEntries;
 
-  std::string _cachedTopDirPath;
-
   std::deque<SharedHandle<DiskWriterEntry> > _openedDiskWriterEntries;
 
   size_t _maxOpenFiles;
@@ -117,13 +115,13 @@ private:
 
   void resetDiskWriterEntries();
 
-  void mkdir(const std::string& topDirPath) const;
+  void mkdir(const std::string& storeDir) const;
 
   std::string getTopDirPath() const;
 
   void openIfNot(const SharedHandle<DiskWriterEntry>& entry,
 		 void (DiskWriterEntry::*f)(const std::string&),
-		 const std::string& topDirPath);
+		 const std::string& storeDir);
  
   static const size_t DEFAULT_MAX_OPEN_FILES = 100;
 
@@ -200,6 +198,8 @@ public:
 
   const std::deque<SharedHandle<DiskWriterEntry> >&
   getDiskWriterEntries() const;
+
+  const std::string& getStoreDir() const;
 };
 
 typedef SharedHandle<MultiDiskAdaptor> MultiDiskAdaptorHandle;

+ 1 - 1
src/MultiFileAllocationIterator.cc

@@ -59,7 +59,7 @@ void MultiFileAllocationIterator::allocateChunk()
     FileEntryHandle fileEntry = entry->getFileEntry();
     // Open file before calling DiskWriterEntry::size()
     _diskAdaptor->openIfNot(entry, &DiskWriterEntry::openFile,
-			    _diskAdaptor->getTopDirPath());
+			    _diskAdaptor->getStoreDir());
     entry->enableDirectIO();
     if(entry->needsFileAllocation() && entry->size() < fileEntry->getLength()) {
       // Calling private function of MultiDiskAdaptor.

+ 8 - 0
src/OptionHandlerFactory.cc

@@ -968,6 +968,14 @@ OptionHandlers OptionHandlerFactory::createOptionHandlers()
     op->addTag(TAG_BITTORRENT);
     handlers.push_back(op);
   }
+  {
+    SharedHandle<OptionHandler> op(new IndexOutOptionHandler
+				   (PREF_INDEX_OUT,
+				    TEXT_INDEX_OUT,
+				    'O'));
+    op->addTag(TAG_BITTORRENT);
+    handlers.push_back(op);
+  }
   {
     SharedHandle<OptionHandler> op(new IntegerRangeOptionHandler
 				   (PREF_LISTEN_PORT,

+ 26 - 0
src/OptionHandlerImpl.h

@@ -366,6 +366,32 @@ public:
   }
 };
 
+class IndexOutOptionHandler : public NameMatchOptionHandler {
+private:
+public:
+  IndexOutOptionHandler(const std::string& optName,
+			const std::string& description,
+			char shortName = 0):
+    NameMatchOptionHandler(optName, description, NO_DEFAULT_VALUE,
+			   OptionHandler::REQ_ARG, shortName) {}
+
+  virtual ~IndexOutOptionHandler() {}
+
+  virtual void parseArg(Option& option, const std::string& optarg)
+  {
+    // See optarg is in the fomrat of "INDEX=PATH"
+    Util::parseIndexPath(optarg);
+    std::string value = option.get(_optName);
+    value += optarg+"\n";
+    option.put(_optName, value);
+  }
+
+  virtual std::string createPossibleValuesString() const
+  {
+    return "INDEX=PATH";
+  }
+};
+
 class ParameterOptionHandler : public NameMatchOptionHandler {
 private:
   std::deque<std::string> _validParamValues;

+ 22 - 0
src/Util.cc

@@ -941,4 +941,26 @@ std::string Util::htmlEscape(const std::string& src)
   return dest;
 }
 
+std::map<size_t, std::string>::value_type
+Util::parseIndexPath(const std::string& line)
+{
+  std::pair<std::string, std::string> p = Util::split(line, "=");
+  size_t index = parseUInt(p.first);
+  if(p.second.empty()) {
+    throw DlAbortEx(StringFormat("Path with index=%u is empty.",
+				 static_cast<unsigned int>(index)).str());
+  }
+  return std::map<size_t, std::string>::value_type(index, p.second);
+}
+
+std::map<size_t, std::string> Util::createIndexPathMap(std::istream& i)
+{
+  std::map<size_t, std::string> indexPathMap;
+  std::string line;
+  while(getline(i, line)) {
+    indexPathMap.insert(indexPathMap.begin(), parseIndexPath(line));
+  }
+  return indexPathMap;
+}
+
 } // namespace aria2

+ 8 - 0
src/Util.h

@@ -44,6 +44,7 @@
 #include <deque>
 #include <iosfwd>
 #include <numeric>
+#include <map>
 
 #include "SharedHandle.h"
 #include "IntSequence.h"
@@ -306,6 +307,13 @@ public:
     return std::accumulate(elements.begin()+1, elements.end(), elements[0],
 			   Concat("/"));
   }
+
+  // Parses INDEX=PATH format string. INDEX must be an unsigned
+  // integer.
+  static std::map<size_t, std::string>::value_type
+  parseIndexPath(const std::string& line);
+
+  static std::map<size_t, std::string> createIndexPathMap(std::istream& i);
 };
 
 } // namespace aria2

+ 11 - 1
src/download_helper.cc

@@ -37,6 +37,7 @@
 #include <iostream>
 #include <fstream>
 #include <algorithm>
+#include <sstream>
 
 #include "RequestGroup.h"
 #include "Option.h"
@@ -124,6 +125,13 @@ createBtRequestGroup(const std::string& torrentFilePath,
   btContext->setDir(requestOption.get(PREF_DIR));
   btContext->setFileFilter
     (Util::parseIntRange(requestOption.get(PREF_SELECT_FILE)));
+  std::istringstream indexOutIn(requestOption.get(PREF_INDEX_OUT));
+  std::map<size_t, std::string> indexPathMap =
+    Util::createIndexPathMap(indexOutIn);
+  for(std::map<size_t, std::string>::const_iterator i = indexPathMap.begin();
+      i != indexPathMap.end(); ++i) {
+    btContext->setFilePathWithIndex((*i).first, (*i).second);
+  }
   rg->setDownloadContext(btContext);
   btContext->setOwnerRequestGroup(rg.get());
   return rg;
@@ -295,11 +303,13 @@ static void createRequestGroupForUriList
     if(uris.empty()) {
       continue;
     }
+    // TODO use OptionParser to validate the value in the options.
     // These options can be specified in input list(-i list).
     static const std::string REQUEST_OPTIONS[] = {
       PREF_DIR,
       PREF_OUT,
-      PREF_SELECT_FILE
+      PREF_SELECT_FILE,
+      PREF_INDEX_OUT
     };
     foreachCopyIfndef(&REQUEST_OPTIONS[0],
 		      &REQUEST_OPTIONS[arrayLength(REQUEST_OPTIONS)],

+ 2 - 0
src/prefs.cc

@@ -283,6 +283,8 @@ const std::string PREF_BT_HASH_CHECK_SEED("bt-hash-check-seed");
 const std::string PREF_BT_MAX_PEERS("bt-max-peers");
 // values: a string (IP address)
 const std::string PREF_BT_EXTERNAL_IP("bt-external-ip");
+// values: 1*digit '=' a string that your file system recognizes as a file name.
+const std::string PREF_INDEX_OUT("index-out");
 
 /**
  * Metalink related preferences

+ 2 - 0
src/prefs.h

@@ -287,6 +287,8 @@ extern const std::string PREF_BT_HASH_CHECK_SEED;
 extern const std::string PREF_BT_MAX_PEERS;
 // values: a string (IP address)
 extern const std::string PREF_BT_EXTERNAL_IP;
+// values: 1*digit '=' a string that your file system recognizes as a file name.
+extern const std::string PREF_INDEX_OUT;
 
 /**
  * Metalink related preferences

+ 6 - 0
src/usage_text.h

@@ -493,3 +493,9 @@ _(" --http-auth-challenge[=true|false] Send HTTP authorization header only when
   "                              are embedded in URI, authorization header is\n"\
   "                              always sent to the server regardless of this\n"\
   "                              option.")
+#define TEXT_INDEX_OUT \
+_(" -O, --index-out=INDEX=PATH   Set file path for file with index=INDEX. You can\n"\
+  "                              find the file index using the --show-files option.\n"\
+  "                              PATH is a relative path to the path specified in\n"\
+  "                              --dir option. You can use this option multiple\n"\
+  "                              times.")

+ 4 - 4
test/DefaultBtContextTest.cc

@@ -108,11 +108,11 @@ void DefaultBtContextTest::testGetFileEntries() {
   std::deque<SharedHandle<FileEntry> >::iterator itr = fileEntries.begin();
 
   SharedHandle<FileEntry> fileEntry1 = *itr;
-  CPPUNIT_ASSERT_EQUAL(std::string("aria2/src/aria2c"),
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2-test/aria2/src/aria2c"),
 		       fileEntry1->getPath());
   itr++;
   SharedHandle<FileEntry> fileEntry2 = *itr;
-  CPPUNIT_ASSERT_EQUAL(std::string("aria2-0.2.2.tar.bz2"),
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2-test/aria2-0.2.2.tar.bz2"),
 		       fileEntry2->getPath());
 }
 
@@ -289,7 +289,7 @@ void DefaultBtContextTest::testGetFileEntries_multiFileUrlList() {
   std::deque<SharedHandle<FileEntry> >::iterator itr = fileEntries.begin();
 
   SharedHandle<FileEntry> fileEntry1 = *itr;
-  CPPUNIT_ASSERT_EQUAL(std::string("aria2/src/aria2c"),
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2-test/aria2/src/aria2c"),
 		       fileEntry1->getPath());
   std::deque<std::string> uris1 = fileEntry1->getAssociatedUris();
   CPPUNIT_ASSERT_EQUAL((size_t)2, uris1.size());
@@ -300,7 +300,7 @@ void DefaultBtContextTest::testGetFileEntries_multiFileUrlList() {
 
   itr++;
   SharedHandle<FileEntry> fileEntry2 = *itr;
-  CPPUNIT_ASSERT_EQUAL(std::string("aria2-0.2.2.tar.bz2"),
+  CPPUNIT_ASSERT_EQUAL(std::string("aria2-test/aria2-0.2.2.tar.bz2"),
 		       fileEntry2->getPath());
   std::deque<std::string> uris2 = fileEntry2->getAssociatedUris();
   CPPUNIT_ASSERT_EQUAL((size_t)2, uris2.size());

+ 8 - 11
test/MultiDiskAdaptorTest.cc

@@ -434,9 +434,7 @@ void MultiDiskAdaptorTest::testSize()
 
 void MultiDiskAdaptorTest::testUtime()
 {
-  std::string storeDir = "/tmp";
-  std::string topDir = "aria2_MultiDiskAdaptorTest_testUtime";
-  std::string prefix = storeDir+"/"+topDir;
+  std::string storeDir = "/tmp/aria2_MultiDiskAdaptorTest_testUtime";
   SharedHandle<FileEntry> entries[] = {
     SharedHandle<FileEntry>(new FileEntry("requested", 0, 0)),
     SharedHandle<FileEntry>(new FileEntry("notFound", 0, 0)),
@@ -444,10 +442,10 @@ void MultiDiskAdaptorTest::testUtime()
     SharedHandle<FileEntry>(new FileEntry("anotherRequested", 0, 0)),
   };
 
-  createFile(prefix+"/"+entries[0]->getPath(), entries[0]->getLength());
-  File(prefix+"/"+entries[1]->getPath()).remove();
-  createFile(prefix+"/"+entries[2]->getPath(), entries[2]->getLength());
-  createFile(prefix+"/"+entries[3]->getPath(), entries[3]->getLength());
+  createFile(storeDir+"/"+entries[0]->getPath(), entries[0]->getLength());
+  File(storeDir+"/"+entries[1]->getPath()).remove();
+  createFile(storeDir+"/"+entries[2]->getPath(), entries[2]->getLength());
+  createFile(storeDir+"/"+entries[3]->getPath(), entries[3]->getLength());
 
   entries[2]->setRequested(false);
 
@@ -455,7 +453,6 @@ void MultiDiskAdaptorTest::testUtime()
     (&entries[0], &entries[arrayLength(entries)]);
   MultiDiskAdaptor adaptor;
   adaptor.setStoreDir(storeDir);
-  adaptor.setTopDir(topDir);
   adaptor.setFileEntries(fileEntries);
 
   time_t atime = (time_t) 100000;
@@ -464,14 +461,14 @@ void MultiDiskAdaptorTest::testUtime()
   CPPUNIT_ASSERT_EQUAL((size_t)2, adaptor.utime(Time(atime), Time(mtime)));
   
   CPPUNIT_ASSERT_EQUAL((time_t)mtime,
-		       File(prefix+"/"+entries[0]->getPath())
+		       File(storeDir+"/"+entries[0]->getPath())
 		       .getModifiedTime().getTime());
 
   CPPUNIT_ASSERT_EQUAL((time_t)mtime,
-		       File(prefix+"/"+entries[3]->getPath())
+		       File(storeDir+"/"+entries[3]->getPath())
 		       .getModifiedTime().getTime());
 
-  CPPUNIT_ASSERT((time_t)mtime != File(prefix+"/"+entries[2]->getPath())
+  CPPUNIT_ASSERT((time_t)mtime != File(storeDir+"/"+entries[2]->getPath())
 		 .getModifiedTime().getTime());
 }
 

+ 45 - 46
test/MultiFileAllocationIteratorTest.cc

@@ -1,4 +1,10 @@
 #include "MultiFileAllocationIterator.h"
+
+#include <algorithm>
+#include <iostream>
+
+#include <cppunit/extensions/HelperMacros.h>
+
 #include "File.h"
 #include "MultiDiskAdaptor.h"
 #include "FileEntry.h"
@@ -6,9 +12,6 @@
 #include "array_fun.h"
 #include "TestUtil.h"
 #include "DiskWriter.h"
-#include <algorithm>
-#include <iostream>
-#include <cppunit/extensions/HelperMacros.h>
 
 namespace aria2 {
 
@@ -53,20 +56,17 @@ void MultiFileAllocationIteratorTest::testMakeDiskWriterEntries()
   fs[8]->setRequested(false); // file9
   fs[9]->setRequested(false); // fileA
 
-  std::string topDir = "top";
   std::string storeDir = "/tmp/aria2_MultiFileAllocationIteratorTest"
     "_testMakeDiskWriterEntries";
-  std::string prefix = storeDir+"/"+topDir;
 
   // create empty file4
-  createFile(prefix+std::string("/file4"), 0);
+  createFile(storeDir+std::string("/file4"), 0);
   
   SharedHandle<MultiDiskAdaptor> diskAdaptor(new MultiDiskAdaptor());
   diskAdaptor->setFileEntries
     (std::deque<SharedHandle<FileEntry> >(&fs[0], &fs[arrayLength(fs)]));
   diskAdaptor->setPieceLength(1024);
   diskAdaptor->setStoreDir(storeDir);
-  diskAdaptor->setTopDir(topDir);
   diskAdaptor->openFile();
 
   SharedHandle<MultiFileAllocationIterator> itr
@@ -78,67 +78,67 @@ void MultiFileAllocationIteratorTest::testMakeDiskWriterEntries()
   CPPUNIT_ASSERT_EQUAL((size_t)11, entries.size());
 
   // file1
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file1"),
-		       entries[0]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file1"),
+		       entries[0]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[0]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[0]->getDiskWriter().isNull());
   // file2
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file2"),
-		       entries[1]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file2"),
+		       entries[1]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[1]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[1]->getDiskWriter().isNull());
   // file3
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file3"),
-		       entries[2]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file3"),
+		       entries[2]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[2]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[2]->getDiskWriter().isNull());
   // file4, diskWriter is not null, because file exists.
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file4"),
-		       entries[3]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file4"),
+		       entries[3]->getFilePath(storeDir));
   CPPUNIT_ASSERT(!entries[3]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[3]->getDiskWriter().isNull());
   // file5
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file5"),
-		       entries[4]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file5"),
+		       entries[4]->getFilePath(storeDir));
   CPPUNIT_ASSERT(!entries[4]->needsFileAllocation());
   CPPUNIT_ASSERT(entries[4]->getDiskWriter().isNull());
   // file6
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file6"),
-		       entries[5]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file6"),
+		       entries[5]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[5]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[5]->getDiskWriter().isNull());
   // file7
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file7"),
-		       entries[6]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file7"),
+		       entries[6]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[6]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[6]->getDiskWriter().isNull());
   // file8
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file8"),
-		       entries[7]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file8"),
+		       entries[7]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[7]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[7]->getDiskWriter().isNull());
   // file9
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/file9"),
-		       entries[8]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/file9"),
+		       entries[8]->getFilePath(storeDir));
   CPPUNIT_ASSERT(!entries[8]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[8]->getDiskWriter().isNull());
   // fileA
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/fileA"),
-		       entries[9]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/fileA"),
+		       entries[9]->getFilePath(storeDir));
   CPPUNIT_ASSERT(!entries[9]->needsFileAllocation());
   CPPUNIT_ASSERT(entries[9]->getDiskWriter().isNull());
   // fileB
-  CPPUNIT_ASSERT_EQUAL(prefix+std::string("/fileB"),
-		       entries[10]->getFilePath(prefix));
+  CPPUNIT_ASSERT_EQUAL(storeDir+std::string("/fileB"),
+		       entries[10]->getFilePath(storeDir));
   CPPUNIT_ASSERT(entries[10]->needsFileAllocation());
   CPPUNIT_ASSERT(!entries[10]->getDiskWriter().isNull());
 }
 
 void MultiFileAllocationIteratorTest::testAllocate()
 {
-  std::string dir = "/tmp";
-  std::string topDir = "aria2_MultiFileAllocationIteratorTest_testAllocate";
-  std::string prefix = dir+"/"+topDir;
+  std::string storeDir =
+    "/tmp/aria2_MultiFileAllocationIteratorTest_testAllocate";
+
   std::string fname1 = "file1";
   std::string fname2 = "file2";
   std::string fname3 = "file3";
@@ -154,8 +154,7 @@ void MultiFileAllocationIteratorTest::testAllocate()
 
   try {
     SharedHandle<MultiDiskAdaptor> diskAdaptor(new MultiDiskAdaptor());
-    diskAdaptor->setStoreDir(dir);
-    diskAdaptor->setTopDir(topDir);
+    diskAdaptor->setStoreDir(storeDir);
     diskAdaptor->setPieceLength(1);
 
     int64_t offset = 0;
@@ -199,12 +198,12 @@ void MultiFileAllocationIteratorTest::testAllocate()
     diskAdaptor->setFileEntries(fs);
 
     
-    File(prefix+"/"+fname1).remove();
-    File(prefix+"/"+fname2).remove();
-    File(prefix+"/"+fname3).remove();
-    File(prefix+"/"+fname4).remove();
-    File(prefix+"/"+fname5).remove();
-    File(prefix+"/"+fname6).remove();
+    File(storeDir+"/"+fname1).remove();
+    File(storeDir+"/"+fname2).remove();
+    File(storeDir+"/"+fname3).remove();
+    File(storeDir+"/"+fname4).remove();
+    File(storeDir+"/"+fname5).remove();
+    File(storeDir+"/"+fname6).remove();
 
     // we have to open file first.
     diskAdaptor->initAndOpenFile();
@@ -214,13 +213,13 @@ void MultiFileAllocationIteratorTest::testAllocate()
     while(!itr->finished()) {
       itr->allocateChunk();
     }
-    CPPUNIT_ASSERT_EQUAL((uint64_t)length1, File(prefix+"/"+fname1).size());
-    CPPUNIT_ASSERT_EQUAL((uint64_t)length2, File(prefix+"/"+fname2).size());
-    CPPUNIT_ASSERT_EQUAL((uint64_t)length3, File(prefix+"/"+fname3).size());
-    CPPUNIT_ASSERT(!File(prefix+"/"+fname4).isFile());
+    CPPUNIT_ASSERT_EQUAL((uint64_t)length1, File(storeDir+"/"+fname1).size());
+    CPPUNIT_ASSERT_EQUAL((uint64_t)length2, File(storeDir+"/"+fname2).size());
+    CPPUNIT_ASSERT_EQUAL((uint64_t)length3, File(storeDir+"/"+fname3).size());
+    CPPUNIT_ASSERT(!File(storeDir+"/"+fname4).isFile());
 
-    CPPUNIT_ASSERT_EQUAL((uint64_t)length5, File(prefix+"/"+fname5).size());
-    CPPUNIT_ASSERT(!File(prefix+"/"+fname6).isFile());
+    CPPUNIT_ASSERT_EQUAL((uint64_t)length5, File(storeDir+"/"+fname5).size());
+    CPPUNIT_ASSERT(!File(storeDir+"/"+fname6).isFile());
 
   } catch(Exception& e) {
     CPPUNIT_FAIL(e.stackTrace());

+ 36 - 0
test/UtilTest.cc

@@ -54,6 +54,8 @@ class UtilTest:public CppUnit::TestFixture {
   CPPUNIT_TEST(testUrlencode);
   CPPUNIT_TEST(testHtmlEscape);
   CPPUNIT_TEST(testJoinPath);
+  CPPUNIT_TEST(testParseIndexPath);
+  CPPUNIT_TEST(testCreateIndexPathMap);
   CPPUNIT_TEST_SUITE_END();
 private:
 
@@ -97,6 +99,8 @@ public:
   void testUrlencode();
   void testHtmlEscape();
   void testJoinPath();
+  void testParseIndexPath();
+  void testCreateIndexPathMap();
 };
 
 
@@ -778,4 +782,36 @@ void UtilTest::testJoinPath()
 				      &parentdot[arrayLength(parentdot)]));
 }
 
+void UtilTest::testParseIndexPath()
+{
+  std::map<size_t, std::string>::value_type p = Util::parseIndexPath("1=foo");
+  CPPUNIT_ASSERT_EQUAL((size_t)1, p.first);
+  CPPUNIT_ASSERT_EQUAL(std::string("foo"), p.second);
+  try {
+    Util::parseIndexPath("1X=foo");
+    CPPUNIT_FAIL("exception must be thrown.");
+  } catch(Exception& e) {
+    // success
+  }
+  try {
+    Util::parseIndexPath("1=");
+    CPPUNIT_FAIL("exception must be thrown.");
+  } catch(Exception& e) {
+    // success
+  }
+}
+
+void UtilTest::testCreateIndexPathMap()
+{
+  std::stringstream in
+    ("1=/tmp/myfile\n"
+     "100=/myhome/mypicture.png\n");
+  std::map<size_t, std::string> m = Util::createIndexPathMap(in);
+  CPPUNIT_ASSERT_EQUAL((size_t)2, m.size());
+  CPPUNIT_ASSERT(m.find(1) != m.end());
+  CPPUNIT_ASSERT_EQUAL(std::string("/tmp/myfile"), m[1]);
+  CPPUNIT_ASSERT(m.find(100) != m.end());
+  CPPUNIT_ASSERT_EQUAL(std::string("/myhome/mypicture.png"), m[100]);
+}
+
 } // namespace aria2