Forráskód Böngészése

2006-04-12 Tatsuhiro Tsujikawa <tujikawa at rednoah dot com>

	To add the ability to download multi torrent into respective 
files
	directly:
	
	* src/DiskWriter.h (openFile): New function.
	(seek): Removed.
	* src/MultiDiskWriter.h: New class.
	* src/MultiDiskWriter.cc: New class.
	* src/AbstractDiskWriter.h (seek): Changed its scope from public 
to
	protected.
	(openFile): New function.
	* src/AbstractDiskWriter.cc (openFile): New function.
	* src/prefs.h (V_FALSE): New definition.
	(PREF_DIRECT_FILE_MAPPING): New definition.
	* src/TorrentMan.h (setupDiskWriter): New function.
	(setAllMultiFileRequestedState): New function.
	(onDownloadComplete): New function.
	* src/TorrentMan.cc : Included MultiDiskWriter.h
	(setupDiskWriter): New function.
	(getFilePath): Updated.
	(getTempFilePath): Updated.
	(getSegmentFilePath): Updated.
	(fixFilename): Updated.
	(deleteTempFile): Updated.
	(setAllMultiFileRequestedState): New function.
	(setFileEntriesToDownload): Use setAllMultiFileRequestedState().
	(finishPartialDownloadingMode): Reset requested flags.
	(onDownloadComplete): New function.
	* src/main.cc: Added --direct-file-mapping option.
	Use TorretMan::setupDiskWriter().
	* src/TorrentDownloadEngine.cc (afterEachIteration): Use 
TorrentMan::
	onDownloadComplete().
	
	
	To fix ETA bug:

	* src/Util.h (difftvsec): New function.
	* src/Util.cc (difftvsec): New function.
	* src/TorrentConsoleDownloadEngine.cc (calculateSpeed): Use int 
for the
	type of "elapsed" instead of long long int.
	(calculateStatistics): Use Util::difftvsec instead of 
Util::difftv.
	The updates of statistics takes place every 1 seconds.
	* src/TorrentConsoleDownloadEngine.h (lastElapsed): Changed its 
type.
	(calculateSpeed): Changed its argument signature.
	
	* src/PeerMessage.cc (toString): Fixed message.
Tatsuhiro Tsujikawa 19 éve
szülő
commit
2f4b3f7d02

+ 48 - 0
ChangeLog

@@ -1,3 +1,51 @@
+2006-04-12  Tatsuhiro Tsujikawa  <tujikawa at rednoah dot com>
+
+	To add the ability to download multi torrent into respective files
+	directly:
+	
+	* src/DiskWriter.h (openFile): New function.
+	(seek): Removed.
+	* src/MultiDiskWriter.h: New class.
+	* src/MultiDiskWriter.cc: New class.
+	* src/AbstractDiskWriter.h (seek): Changed its scope from public to
+	protected.
+	(openFile): New function.
+	* src/AbstractDiskWriter.cc (openFile): New function.
+	* src/prefs.h (V_FALSE): New definition.
+	(PREF_DIRECT_FILE_MAPPING): New definition.
+	* src/TorrentMan.h (setupDiskWriter): New function.
+	(setAllMultiFileRequestedState): New function.
+	(onDownloadComplete): New function.
+	* src/TorrentMan.cc : Included MultiDiskWriter.h
+	(setupDiskWriter): New function.
+	(getFilePath): Updated.
+	(getTempFilePath): Updated.
+	(getSegmentFilePath): Updated.
+	(fixFilename): Updated.
+	(deleteTempFile): Updated.
+	(setAllMultiFileRequestedState): New function.
+	(setFileEntriesToDownload): Use setAllMultiFileRequestedState().
+	(finishPartialDownloadingMode): Reset requested flags.
+	(onDownloadComplete): New function.
+	* src/main.cc: Added --direct-file-mapping option.
+	Use TorretMan::setupDiskWriter().
+	* src/TorrentDownloadEngine.cc (afterEachIteration): Use TorrentMan::
+	onDownloadComplete().
+	
+	
+	To fix ETA bug:
+
+	* src/Util.h (difftvsec): New function.
+	* src/Util.cc (difftvsec): New function.
+	* src/TorrentConsoleDownloadEngine.cc (calculateSpeed): Use int for the
+	type of "elapsed" instead of long long int.
+	(calculateStatistics): Use Util::difftvsec instead of Util::difftv.
+	The updates of statistics takes place every 1 seconds.
+	* src/TorrentConsoleDownloadEngine.h (lastElapsed): Changed its type.
+	(calculateSpeed): Changed its argument signature.
+	
+	* src/PeerMessage.cc (toString): Fixed message.
+	
 2006-04-06  Tatsuhiro Tsujikawa  <tujikawa at rednoah dot com>
 
 	To print ETA:

+ 2 - 1
TODO

@@ -12,4 +12,5 @@
 * Add max peers command-line option
 * Distinguish seeder from leecher
 * file selection in multi-file mode
-* try to use ftruncate to allocate file.
+* try to use ftruncate to allocate file.
+* fix TorrentMan::getFilePath()

+ 6 - 0
src/AbstractDiskWriter.cc

@@ -44,6 +44,12 @@ AbstractDiskWriter::~AbstractDiskWriter() {
 #endif // ENABLE_SHA1DIGEST
 }
 
+void AbstractDiskWriter::openFile(const string& filename) {
+  if((fd = open(filename.c_str(), O_CREAT|O_RDWR, S_IRUSR|S_IWUSR)) < 0) {
+    throw new DlAbortEx(strerror(errno));
+  }  
+}
+
 void AbstractDiskWriter::closeFile() {
   if(fd != 0) {
     close(fd);

+ 5 - 2
src/AbstractDiskWriter.h

@@ -39,18 +39,21 @@ protected:
 
   void writeDataInternal(const char* data, int len);
   int readDataInternal(char* data, int len);
+
+  void seek(long long int offset);
+
 public:
   AbstractDiskWriter();
   virtual ~AbstractDiskWriter();
 
+  void openFile(const string& filename);
+
   void closeFile();
 
   void openExistingFile(string filename);
 
   string sha1Sum(long long int offset, long long int length);
 
-  void seek(long long int offset);
-
   void writeData(const char* data, int len, long long int offset);
 
   int readData(char* data, int len, long long int offset);

+ 2 - 1
src/DiskWriter.h

@@ -41,6 +41,8 @@ public:
    */
   virtual void initAndOpenFile(string filename) = 0;
 
+  virtual void openFile(const string& filename) = 0;
+
   /**
    * Closes this output stream.
    */
@@ -69,7 +71,6 @@ public:
 
   virtual string sha1Sum(long long int offset, long long int length) = 0;
 
-  virtual void seek(long long int offset) = 0;
 };
 
 #endif // _D_DISK_WRITER_H_

+ 2 - 1
src/Makefile.am

@@ -78,7 +78,8 @@ SRCS =  Socket.cc Socket.h\
 	Directory.cc Directory.h\
 	TrackerWatcherCommand.cc TrackerWatcherCommand.h\
 	messageDigest.h\
-	SendMessageQueue.cc SendMessageQueue.h
+	SendMessageQueue.cc SendMessageQueue.h\
+	MultiDiskWriter.cc MultiDiskWriter.h
 noinst_LIBRARIES = libaria2c.a
 libaria2c_a_SOURCES = $(SRCS)
 aria2c_LDADD = libaria2c.a @LIBINTL@ @ALLOCA@ @LIBGNUTLS_LIBS@\

+ 4 - 2
src/Makefile.in

@@ -98,7 +98,7 @@ am__objects_1 = Socket.$(OBJEXT) SocketCore.$(OBJEXT) \
 	PeerMessage.$(OBJEXT) Piece.$(OBJEXT) RequestSlot.$(OBJEXT) \
 	RequestSlotMan.$(OBJEXT) TorrentAutoSaveCommand.$(OBJEXT) \
 	Directory.$(OBJEXT) TrackerWatcherCommand.$(OBJEXT) \
-	SendMessageQueue.$(OBJEXT)
+	SendMessageQueue.$(OBJEXT) MultiDiskWriter.$(OBJEXT)
 am_libaria2c_a_OBJECTS = $(am__objects_1)
 libaria2c_a_OBJECTS = $(am_libaria2c_a_OBJECTS)
 am__installdirs = "$(DESTDIR)$(bindir)"
@@ -326,7 +326,8 @@ SRCS = Socket.cc Socket.h\
 	Directory.cc Directory.h\
 	TrackerWatcherCommand.cc TrackerWatcherCommand.h\
 	messageDigest.h\
-	SendMessageQueue.cc SendMessageQueue.h
+	SendMessageQueue.cc SendMessageQueue.h\
+	MultiDiskWriter.cc MultiDiskWriter.h
 
 noinst_LIBRARIES = libaria2c.a
 libaria2c_a_SOURCES = $(SRCS)
@@ -443,6 +444,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/InitiateConnectionCommandFactory.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/List.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MetaFileUtil.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MultiDiskWriter.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Option.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/Peer.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PeerAbstractCommand.Po@am__quote@

+ 251 - 0
src/MultiDiskWriter.cc

@@ -0,0 +1,251 @@
+/* <!-- copyright */
+/*
+ * aria2 - a simple utility for downloading files faster
+ *
+ * Copyright (C) 2006 Tatsuhiro Tsujikawa
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* copyright --> */
+#include "MultiDiskWriter.h"
+#include "DlAbortEx.h"
+#include "Util.h"
+#include <errno.h>
+
+MultiDiskWriter::MultiDiskWriter() {
+#ifdef ENABLE_SHA1DIGEST
+  sha1DigestInit(ctx);
+#endif // ENABLE_SHA1DIGEST
+}
+
+MultiDiskWriter::~MultiDiskWriter() {
+  clearEntries();
+#ifdef ENABLE_SHA1DIGEST
+  sha1DigestFree(ctx);
+#endif // ENABLE_SHA1DIGEST
+}
+
+typedef struct {
+  long long int blockOffset;
+  long long int length;
+} Range;
+
+typedef deque<Range> Ranges;
+
+void MultiDiskWriter::clearEntries() {
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end(); itr++) {
+    if((*itr)->enabled) {
+      (*itr)->diskWriter->closeFile();
+    }
+    delete *itr;
+  }
+  diskWriterEntries.clear();
+}
+
+void MultiDiskWriter::setMultiFileEntries(const MultiFileEntries& multiFileEntries, int pieceLength) {
+  clearEntries();
+  Ranges ranges;
+  for(MultiFileEntries::const_iterator itr = multiFileEntries.begin();
+      itr != multiFileEntries.end(); itr++) {
+    if(itr->requested) {
+      Range range;
+      range.blockOffset = (itr->offset/pieceLength)*pieceLength;
+      range.length = ((itr->offset+itr->length)/pieceLength+
+	((itr->offset+itr->length)%pieceLength ? 1 : 0))*pieceLength-range.blockOffset;
+      ranges.push_back(range);
+    }		
+  }
+  Ranges::const_iterator ritr = ranges.begin();
+  for(MultiFileEntries::const_iterator itr = multiFileEntries.begin();
+      itr != multiFileEntries.end(); itr++) {
+    DiskWriterEntry* entry;
+    if(ritr != ranges.end() &&
+       //       !(ritr->blockOffset+ritr->length-1 < itr->offset ||
+       //	 itr->offset+itr->length-1 < ritr->blockOffset)) {
+       itr->offset < ritr->blockOffset+ritr->length &&
+       ritr->blockOffset < itr->offset+itr->length) {
+
+      entry = new DiskWriterEntry(*itr, true);
+      for(;ritr->blockOffset+ritr->length <= itr->offset+itr->length &&
+	    ritr != ranges.end(); ritr++);
+    } else {
+      entry = new DiskWriterEntry(*itr, false);
+    }
+    diskWriterEntries.push_back(entry);
+  }
+}
+
+void MultiDiskWriter::openFile(const string& filename) {
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end(); itr++) {
+    if((*itr)->enabled) {
+      (*itr)->diskWriter->openFile(filename+"/"+(*itr)->fileEntry.path);
+    }
+  }
+}
+
+// filename is a directory which is specified by the user in the option.
+void MultiDiskWriter::initAndOpenFile(string filename) {
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end(); itr++) {
+    if((*itr)->enabled) {
+      (*itr)->diskWriter->initAndOpenFile(filename+"/"+(*itr)->fileEntry.path);
+    }
+  }
+}
+
+void MultiDiskWriter::closeFile() {
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end(); itr++) {
+    if((*itr)->enabled) {
+      (*itr)->diskWriter->closeFile();
+    }
+  }
+}
+
+void MultiDiskWriter::openExistingFile(string filename) {
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end(); itr++) {
+    if((*itr)->enabled) {
+      (*itr)->diskWriter->openExistingFile(filename+"/"+(*itr)->fileEntry.path);
+    }
+  }
+}
+
+void MultiDiskWriter::writeData(const char* data, int len, long long int position) {
+  long long int offset = position;
+  long long int fileOffset = offset;
+  bool writing = false;
+  int rem = len;
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end() && rem != 0; itr++) {
+    if(isInRange(*itr, offset) || writing) {
+      if(!(*itr)->enabled) {
+	throw new DlAbortEx("invalid offset or length. offset = %lld", offset);
+      }
+      int writeLength = calculateLength(*itr, fileOffset, rem);
+      (*itr)->diskWriter->writeData(data+(len-rem), writeLength, fileOffset);
+      rem -= writeLength;
+      writing = true;
+      fileOffset = 0;
+    } else {
+      fileOffset -= (*itr)->fileEntry.length;
+    }
+  }
+  if(!writing) {
+    throw new DlAbortEx("offset out of range");
+  }
+}
+
+bool MultiDiskWriter::isInRange(const DiskWriterEntry* entry, long long int offset) const {
+  return entry->fileEntry.offset <= offset &&
+    offset < entry->fileEntry.offset+entry->fileEntry.length;
+}
+
+int MultiDiskWriter::calculateLength(const DiskWriterEntry* entry, long long int fileOffset, int rem) const {
+  int length;
+  if(entry->fileEntry.length < fileOffset+rem) {
+    length = entry->fileEntry.length-fileOffset;
+  } else {
+    length = rem;
+  }
+  return length;
+}
+
+int MultiDiskWriter::readData(char* data, int len, long long int position) {
+  long long int offset = position;
+  long long int fileOffset = offset;
+  bool reading = false;
+  int rem = len;
+  int totalReadLength = 0;
+  for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+      itr != diskWriterEntries.end() && rem != 0; itr++) {
+    if(isInRange(*itr, offset) || reading) {
+      if(!(*itr)->enabled) {
+	throw new DlAbortEx("invalid offset or length. offset = %lld", offset);
+      }
+      int readLength = calculateLength((*itr), fileOffset, rem);
+      totalReadLength += (*itr)->diskWriter->readData(data+(len-rem), readLength, fileOffset);
+      rem -= readLength;
+      reading = true;
+      fileOffset = 0;
+    } else {
+      fileOffset -= (*itr)->fileEntry.length;
+    }
+  }
+  if(!reading) {
+    throw new DlAbortEx("offset out of range");
+  }
+  return totalReadLength;
+}
+
+#ifdef ENABLE_SHA1DIGEST
+void MultiDiskWriter::hashUpdate(const DiskWriterEntry* entry, long long int offset, long long int length) const {
+  int BUFSIZE = 16*1024;
+  char buf[BUFSIZE];
+  for(int i = 0; i < length/BUFSIZE; i++) {
+    if(BUFSIZE != entry->diskWriter->readData(buf, BUFSIZE, offset)) {
+      throw "error";
+    }
+    sha1DigestUpdate(ctx, buf, BUFSIZE);
+    offset += BUFSIZE;
+  }
+  int r = length%BUFSIZE;
+  if(r > 0) {
+    if(r != entry->diskWriter->readData(buf, r, offset)) {
+      throw "error";
+    }
+    sha1DigestUpdate(ctx, buf, r);
+  }
+}
+#endif // ENABLE_SHA1DIGEST
+
+string MultiDiskWriter::sha1Sum(long long int offset, long long int length) {
+#ifdef ENABLE_SHA1DIGEST
+  long long int fileOffset = offset;
+  bool reading = false;
+  int rem = length;
+  sha1DigestReset(ctx);
+  try {
+    for(DiskWriterEntries::iterator itr = diskWriterEntries.begin();
+	itr != diskWriterEntries.end() && rem != 0; itr++) {
+      if(isInRange(*itr, offset) || reading) {
+	if(!(*itr)->enabled) {
+	  throw new DlAbortEx("invalid offset or length. offset = %lld", offset);
+	}
+	int readLength = calculateLength((*itr), fileOffset, rem);
+	hashUpdate(*itr, fileOffset, readLength);
+	rem -= readLength;
+	reading = true;
+	fileOffset = 0;
+      } else {
+	fileOffset -= (*itr)->fileEntry.length;
+      }
+    }
+    if(!reading) {
+      throw new DlAbortEx("offset out of range");
+    }
+    unsigned char hashValue[20];
+    sha1DigestFinal(ctx, hashValue);
+    return Util::toHex(hashValue, 20);
+  } catch(string ex) {
+    throw new DlAbortEx(strerror(errno));
+  }
+#else
+  return "";
+#endif // ENABLE_SHA1DIGEST
+}
+

+ 76 - 0
src/MultiDiskWriter.h

@@ -0,0 +1,76 @@
+/* <!-- copyright */
+/*
+ * aria2 - a simple utility for downloading files faster
+ *
+ * Copyright (C) 2006 Tatsuhiro Tsujikawa
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/* copyright --> */
+#ifndef _D_MULTI_DISK_WRITER_H_
+#define _D_MULTI_DISK_WRITER_H_
+
+#include "DefaultDiskWriter.h"
+#include "TorrentMan.h"
+#include "messageDigest.h"
+
+class DiskWriterEntry {
+public:
+  FileEntry fileEntry;
+  DiskWriter* diskWriter;
+  bool enabled;
+public:
+  DiskWriterEntry(const FileEntry& fileEntry, bool enabled):fileEntry(fileEntry), enabled(enabled) {
+    if(enabled) {
+      diskWriter = new DefaultDiskWriter();
+    }
+  }
+  ~DiskWriterEntry() {
+    if(enabled) {
+      delete diskWriter;
+    }
+  }
+};
+
+typedef deque<DiskWriterEntry*> DiskWriterEntries;
+
+class MultiDiskWriter : public DiskWriter {
+private:
+  DiskWriterEntries diskWriterEntries;
+
+  bool isInRange(const DiskWriterEntry* entry, long long int offset) const;
+  int calculateLength(const DiskWriterEntry* entry, long long int fileOffset, int rem) const;
+  void clearEntries();
+#ifdef ENABLE_SHA1DIGEST
+  MessageDigestContext ctx;
+  void hashUpdate(const DiskWriterEntry* entry, long long int offset, long long int length) const;
+#endif // ENABLE_SHA1DIGEST
+
+public:
+  MultiDiskWriter();
+  virtual ~MultiDiskWriter();
+
+  void setMultiFileEntries(const MultiFileEntries& multiFileEntries, int pieceLength);
+
+  virtual void openFile(const string& filename);
+  virtual void initAndOpenFile(string filename);
+  virtual void closeFile();
+  virtual void openExistingFile(string filename);
+  virtual void writeData(const char* data, int len, long long int position = 0);
+  virtual int readData(char* data, int len, long long int position);
+  virtual string sha1Sum(long long int offset, long long int length);
+};
+
+#endif // _D_MULTI_DISK_WRITER_H_

+ 1 - 1
src/PeerMessage.cc

@@ -61,7 +61,7 @@ string PeerMessage::toString() const {
     return "piece index="+Util::itos(index)+", begin="+Util::itos(begin)+
       ", length="+Util::itos(blockLength);
   case CANCEL:
-    return "calcel index="+Util::itos(index)+", begin="+Util::itos(begin)+
+    return "cancel index="+Util::itos(index)+", begin="+Util::itos(begin)+
       ", length="+Util::itos(length);
   case KEEP_ALIVE:
     return "keep alive";

+ 15 - 12
src/TorrentConsoleDownloadEngine.cc

@@ -74,15 +74,15 @@ void TorrentConsoleDownloadEngine::initStatistics() {
   }
 }
 
-int TorrentConsoleDownloadEngine::calculateSpeed(long long int sessionLength, long long int elapsed) {
-  int nowSpeed = (int)(sessionLength/(elapsed/1000000.0));
+int TorrentConsoleDownloadEngine::calculateSpeed(long long int sessionLength, int elapsed) {
+  int nowSpeed = (int)(sessionLength/(elapsed*1.0));
   return nowSpeed;
 }
 
 void TorrentConsoleDownloadEngine::calculateStatistics() {
   struct timeval now;
   gettimeofday(&now, NULL);
-  long long int elapsed = Util::difftv(now, cp[currentCp]);
+  int elapsed = Util::difftvsec(now, cp[currentCp]);
 
   sessionDownloadLengthArray[0] += torrentMan->getDeltaDownloadLength();
   sessionUploadLengthArray[0] += torrentMan->getDeltaUploadLength();
@@ -91,8 +91,6 @@ void TorrentConsoleDownloadEngine::calculateStatistics() {
 
   sessionDownloadLength += torrentMan->getDeltaDownloadLength();
 
-  downloadSpeed = calculateSpeed(sessionDownloadLengthArray[currentCp], elapsed);
-  uploadSpeed = calculateSpeed(sessionUploadLengthArray[currentCp], elapsed);
 
   torrentMan->resetDeltaDownloadLength();
   torrentMan->resetDeltaUploadLength();
@@ -105,18 +103,23 @@ void TorrentConsoleDownloadEngine::calculateStatistics() {
     totalLength = torrentMan->getTotalLength();
   }
   
-  avgSpeed = calculateSpeed(sessionDownloadLength,
-			    Util::difftv(now, startup));
-  if(avgSpeed != 0) {
-    eta = (totalLength-downloadLength)/avgSpeed;
-  }
 
-  if(elapsed-lastElapsed >= 1000000) {
+  if(elapsed-lastElapsed >= 1) {
+    downloadSpeed = calculateSpeed(sessionDownloadLengthArray[currentCp], elapsed);
+    uploadSpeed = calculateSpeed(sessionUploadLengthArray[currentCp], elapsed);
+    avgSpeed = calculateSpeed(sessionDownloadLength,
+			      Util::difftvsec(now, startup));
+    if(avgSpeed < 0) {
+      avgSpeed = 0;
+    } else if(avgSpeed != 0) {
+      eta = (totalLength-downloadLength)/avgSpeed;
+    }
+
     printStatistics();
     lastElapsed = elapsed;
   }
 
-  if(elapsed > 15*1000000) {
+  if(elapsed > 15) {
     sessionDownloadLengthArray[currentCp] = 0;
     sessionUploadLengthArray[currentCp] = 0;
     cp[currentCp] = now;

+ 2 - 2
src/TorrentConsoleDownloadEngine.h

@@ -38,7 +38,7 @@ private:
 
   int downloadSpeed;
   int uploadSpeed;
-  long long int lastElapsed;
+  int lastElapsed;
   long long int partialDownloadLengthDiff;
   long long int partialTotalLength;
   struct timeval startup;
@@ -49,7 +49,7 @@ private:
   long long int totalLength;
 
   void printStatistics();
-  int calculateSpeed(long long int sessionLength, long long int elapsed);
+  int calculateSpeed(long long int sessionLength, int elapsed);
 protected:
   void initStatistics();
   void calculateStatistics();

+ 3 - 9
src/TorrentDownloadEngine.cc

@@ -32,18 +32,12 @@ void TorrentDownloadEngine::onEndOfRun() {
 
 void TorrentDownloadEngine::afterEachIteration() {
   if(!filenameFixed && torrentMan->downloadComplete()) {
-    torrentMan->diskWriter->closeFile();
-    torrentMan->save();
-    torrentMan->fixFilename();
     if(torrentMan->isPartialDownloadingMode()) {
-      torrentMan->finishPartialDownloadingMode();
       onPartialDownloadingCompletes();
-      if(torrentMan->downloadComplete()) {
-	filenameFixed = true;
-      }
-    } else {
+    }
+    torrentMan->onDownloadComplete();
+    if(torrentMan->downloadComplete()) {
       filenameFixed = true;
     }
-    torrentMan->diskWriter->openExistingFile(torrentMan->getTempFilePath());
   }
 }

+ 75 - 20
src/TorrentMan.cc

@@ -30,6 +30,7 @@
 #include "message.h"
 #include "PreAllocationDiskWriter.h"
 #include "DefaultDiskWriter.h"
+#include "MultiDiskWriter.h"
 #include "prefs.h"
 #include <errno.h>
 #include <libgen.h>
@@ -379,17 +380,33 @@ void TorrentMan::setup(string metaInfoFile) {
 
   initBitfield();
   delete topDic;
+}
 
-  if(option->get(PREF_NO_PREALLOCATION) == V_TRUE) {
-    diskWriter = new DefaultDiskWriter();
-  } else {
-    diskWriter = new PreAllocationDiskWriter(totalLength);
-  }
-  if(segmentFileExists()) {
-    load();
-    diskWriter->openExistingFile(getTempFilePath());
+void TorrentMan::setupDiskWriter() {
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE) {
+    if(segmentFileExists()) {
+      load();
+    }
+    if(fileMode == SINGLE) {
+      diskWriter = new DefaultDiskWriter();
+    } else {
+      diskWriter = new MultiDiskWriter();
+      ((MultiDiskWriter*)diskWriter)->setMultiFileEntries(multiFileEntries, pieceLength);
+      multiFileTopDir->createDir(storeDir, true);
+    }
+    diskWriter->openFile(getFilePath());
   } else {
-    diskWriter->initAndOpenFile(getTempFilePath());
+    if(option->get(PREF_NO_PREALLOCATION) == V_TRUE) {
+      diskWriter = new DefaultDiskWriter();
+    } else {
+      diskWriter = new PreAllocationDiskWriter(totalLength);
+    }
+    if(segmentFileExists()) {
+      load();
+      diskWriter->openExistingFile(getTempFilePath());
+    } else {
+      diskWriter->initAndOpenFile(getTempFilePath());
+    }
   }
   setupComplete = true;
 }
@@ -417,15 +434,27 @@ string TorrentMan::getPieceHash(int index) const {
 }
 
 string TorrentMan::getFilePath() const {
-  return storeDir+"/"+name;
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE && fileMode == MULTI) {
+    return storeDir;
+  } else {
+    return storeDir+"/"+name;
+  }
 }
 
 string TorrentMan::getTempFilePath() const {
-  return getFilePath()+".a2tmp";
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE) {
+    return getFilePath();
+  } else {
+    return getFilePath()+".a2tmp";
+  }
 }
 
 string TorrentMan::getSegmentFilePath() const {
-  return getFilePath()+".aria2";
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE && fileMode == MULTI) {
+    return storeDir+"/"+name+".aria2";
+  } else {
+    return getFilePath()+".aria2";
+  }
 }
 
 bool TorrentMan::segmentFileExists() const {
@@ -518,10 +547,14 @@ void TorrentMan::remove() const {
 }
 
 void TorrentMan::fixFilename() {
-  if(fileMode == SINGLE) {
-    copySingleFile();
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE) {
+    // nothing to do here
   } else {
-    splitMultiFile();
+    if(fileMode == SINGLE) {
+      copySingleFile();
+    } else {
+      splitMultiFile();
+    }
   }
 }
 
@@ -547,7 +580,11 @@ void TorrentMan::splitMultiFile() {
 }
 
 void TorrentMan::deleteTempFile() const {
-  unlink(getTempFilePath().c_str());
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE) {
+    // nothing to do here
+  } else {
+    unlink(getTempFilePath().c_str());
+  }
 }
 
 // bool TorrentMan::unextractedFileEntryExists() const {
@@ -567,10 +604,7 @@ void TorrentMan::setFileEntriesToDownload(const Strings& filePaths) {
     throw new DlAbortEx("only multi-mode supports partial downloading mode.");
   }
   // clear all requested flags in multiFileEntries.
-  for(MultiFileEntries::iterator itr = multiFileEntries.begin();
-      itr != multiFileEntries.end(); itr++) {
-    itr->requested = false;
-  }
+  setAllMultiFileRequestedState(false);
   for(Strings::const_iterator pitr = filePaths.begin();
       pitr != filePaths.end(); pitr++) {
     bool found = false;
@@ -594,8 +628,19 @@ bool TorrentMan::isPartialDownloadingMode() const {
   return bitfield->isFilterEnabled();
 }
 
+void TorrentMan::setAllMultiFileRequestedState(bool state) {
+  for(MultiFileEntries::iterator itr = multiFileEntries.begin();
+      itr != multiFileEntries.end(); itr++) {
+    itr->requested = state;
+  }  
+}
+
 void TorrentMan::finishPartialDownloadingMode() {
   bitfield->clearFilter();
+  setAllMultiFileRequestedState(true);
+  if(option->get(PREF_DIRECT_FILE_MAPPING) == V_TRUE && fileMode == MULTI) {
+    ((MultiDiskWriter*)diskWriter)->setMultiFileEntries(multiFileEntries, pieceLength);
+  }
 }
 
 long long int TorrentMan::getCompletedLength() const {
@@ -605,3 +650,13 @@ long long int TorrentMan::getCompletedLength() const {
 long long int TorrentMan::getPartialTotalLength() const {
   return bitfield->getFilteredTotalLength();
 }
+
+void TorrentMan::onDownloadComplete() {
+  diskWriter->closeFile();
+  save();
+  fixFilename();
+  if(isPartialDownloadingMode()) {
+    finishPartialDownloadingMode();
+  }
+  diskWriter->openFile(getTempFilePath());
+}

+ 5 - 1
src/TorrentMan.h

@@ -151,6 +151,7 @@ public:
   }
 
   void setup(string metaInfoFile);
+  void setupDiskWriter();
 
   string getPieceHash(int index) const;
 
@@ -237,7 +238,8 @@ public:
   string getName() const;
 
   //bool unextractedFileEntryExists() const;
-
+  
+  void setAllMultiFileRequestedState(bool state);
   void finishPartialDownloadingMode();
   bool isPartialDownloadingMode() const;
 
@@ -246,6 +248,8 @@ public:
   long long int getCompletedLength() const;
   long long int getPartialTotalLength() const;
 
+  void onDownloadComplete();
+
   enum FILE_MODE {
     SINGLE,
     MULTI

+ 7 - 0
src/Util.cc

@@ -90,6 +90,13 @@ long long int Util::difftv(struct timeval tv1, struct timeval tv2) {
 	  tv1.tv_usec-tv2.tv_usec);
 }
 
+int Util::difftvsec(struct timeval tv1, struct timeval tv2) {
+  if(tv1.tv_sec < tv2.tv_sec) {
+    return 0;
+  }
+  return tv1.tv_sec-tv2.tv_sec;
+}
+
 void Util::slice(Strings& result, const string& src, char delim) {
   string::size_type p = 0;
   while(1) {

+ 1 - 0
src/Util.h

@@ -43,6 +43,7 @@ public:
    * If tv1 is older than tv2, then this method returns 0.
    */
   static long long int difftv(struct timeval tv1, struct timeval tv2);
+  static int difftvsec(struct timeval tv1, struct timeval tv2);
   /**
    * Take a string src which is a deliminated list and add its elements
    * into result. result is not cleared before conversion begins.

+ 13 - 0
src/main.cc

@@ -252,6 +252,7 @@ int main(int argc, char* argv[]) {
   op->put(PREF_FTP_TYPE, V_BINARY);
   op->put(PREF_FTP_VIA_HTTP_PROXY, V_TUNNEL);
   op->put(PREF_AUTO_SAVE_INTERVAL, "60");
+  op->put(PREF_DIRECT_FILE_MAPPING, V_TRUE);
 
   while(1) {
     int optIndex = 0;
@@ -284,6 +285,7 @@ int main(int argc, char* argv[]) {
       { "follow-torrent", required_argument, &lopt, 16 },
       { "torrent-show-files", no_argument, &lopt, 17 },
       { "no-preallocation", no_argument, &lopt, 18 },
+      { "direct-file-mapping", required_argument, &lopt, 19 },
 #endif // ENABLE_BITTORRENT
       { "version", no_argument, NULL, 'v' },
       { "help", no_argument, NULL, 'h' },
@@ -417,6 +419,16 @@ int main(int argc, char* argv[]) {
       case 18:
 	op->put(PREF_NO_PREALLOCATION, V_TRUE);
 	break;
+      case 19:
+	if(string(optarg) == "true") {
+	  op->put(PREF_DIRECT_FILE_MAPPING, V_TRUE);
+	} else if(string(optarg) == "false") {
+	  op->put(PREF_DIRECT_FILE_MAPPING, V_FALSE);
+	} else {
+	  cerr << "direct-file-mapping must be either 'true' or 'false'." << endl;
+	  showUsage();
+	  exit(1);
+	}
       }
       break;
     }
@@ -621,6 +633,7 @@ int main(int argc, char* argv[]) {
 	   te->torrentMan->getFileMode() == TorrentMan::MULTI) {
 	  te->torrentMan->setFileEntriesToDownload(args);
 	}
+	te->torrentMan->setupDiskWriter();
       }
       PeerListenCommand* listenCommand =
 	new PeerListenCommand(te->torrentMan->getNewCuid(), te);

+ 4 - 1
src/prefs.h

@@ -28,7 +28,7 @@
  * Constants
  */
 #define V_TRUE "true"
-//#define V_FALSE "false"
+#define V_FALSE "false"
 
 /**
  * General preferences
@@ -92,4 +92,7 @@
 #define PREF_TORRENT_SHOW_FILES "torrent_show_files"
 // values: true | false
 #define PREF_NO_PREALLOCATION "no_preallocation"
+// values: true | false
+#define PREF_DIRECT_FILE_MAPPING "direct_file_mapping"
+
 #endif // _D_PREFS_H_

+ 2 - 1
test/Makefile.am

@@ -16,7 +16,8 @@ aria2c_SOURCES = AllTest.cc\
 	TorrentManTest.cc\
 	PeerMessageUtilTest.cc\
 	BitfieldManTest.cc\
-	DefaultDiskWriterTest.cc
+	DefaultDiskWriterTest.cc\
+	MultiDiskWriterTest.cc
 #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64
 #aria2c_LDFLAGS = ${CPPUNIT_LIBS}
 

+ 5 - 2
test/Makefile.in

@@ -63,7 +63,8 @@ am_aria2c_OBJECTS = AllTest.$(OBJEXT) RequestTest.$(OBJEXT) \
 	DictionaryTest.$(OBJEXT) ListTest.$(OBJEXT) \
 	MetaFileUtilTest.$(OBJEXT) ShaVisitorTest.$(OBJEXT) \
 	TorrentManTest.$(OBJEXT) PeerMessageUtilTest.$(OBJEXT) \
-	BitfieldManTest.$(OBJEXT) DefaultDiskWriterTest.$(OBJEXT)
+	BitfieldManTest.$(OBJEXT) DefaultDiskWriterTest.$(OBJEXT) \
+	MultiDiskWriterTest.$(OBJEXT)
 aria2c_OBJECTS = $(am_aria2c_OBJECTS)
 am__DEPENDENCIES_1 =
 aria2c_DEPENDENCIES = ../src/libaria2c.a $(am__DEPENDENCIES_1)
@@ -220,7 +221,8 @@ aria2c_SOURCES = AllTest.cc\
 	TorrentManTest.cc\
 	PeerMessageUtilTest.cc\
 	BitfieldManTest.cc\
-	DefaultDiskWriterTest.cc
+	DefaultDiskWriterTest.cc\
+	MultiDiskWriterTest.cc
 
 #aria2c_CXXFLAGS = ${CPPUNIT_CFLAGS} -I../src -I../lib -Wall -D_FILE_OFFSET_BITS=64
 #aria2c_LDFLAGS = ${CPPUNIT_LIBS}
@@ -292,6 +294,7 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/FileTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ListTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MetaFileUtilTest.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/MultiDiskWriterTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/OptionTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/PeerMessageUtilTest.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/RequestTest.Po@am__quote@

+ 142 - 0
test/MultiDiskWriterTest.cc

@@ -0,0 +1,142 @@
+#include "MultiDiskWriter.h"
+#include <string>
+#include <cppunit/extensions/HelperMacros.h>
+
+using namespace std;
+
+class MultiDiskWriterTest:public CppUnit::TestFixture {
+
+  CPPUNIT_TEST_SUITE(MultiDiskWriterTest);
+  CPPUNIT_TEST(testWriteData);
+  CPPUNIT_TEST(testReadData);
+  CPPUNIT_TEST(testSha1Sum);
+  CPPUNIT_TEST_SUITE_END();
+private:
+
+public:
+  void setUp() {
+  }
+
+  void testWriteData();
+  void testReadData();
+  void testSha1Sum();
+};
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION( MultiDiskWriterTest );
+
+MultiFileEntries createEntries() {
+  FileEntry entry1("file1.txt", 15, 0);
+  FileEntry entry2("file2.txt", 7, 15);
+  FileEntry entry3("file3.txt", 3, 22);
+  unlink("file1.txt");
+  unlink("file2.txt");
+  unlink("file3.txt");
+  MultiFileEntries entries;
+  entries.push_back(entry1);
+  entries.push_back(entry2);
+  entries.push_back(entry3);
+  return entries;
+}
+
+void readFile(const string& filename, char* buf, int bufLength) {
+  FILE* f = fopen(filename.c_str(), "r");
+  if(f == NULL) {
+    abort();
+  }
+  int retval = fread(buf, bufLength, 1, f);
+  fclose(f);
+  if(retval != 1) {
+    abort();
+  }
+}
+
+void MultiDiskWriterTest::testWriteData() {
+  MultiDiskWriter dw;
+  dw.setMultiFileEntries(createEntries(), 2);
+
+  dw.openFile(".");
+  string msg = "12345";
+  dw.writeData(msg.c_str(), msg.size(), 0);
+  dw.closeFile();
+
+  char buf[128];
+  readFile("file1.txt", buf, 5);
+  buf[5] = '\0';
+  CPPUNIT_ASSERT_EQUAL(msg, string(buf));
+
+  dw.openFile(".");
+  string msg2 = "67890ABCDEF";
+  dw.writeData(msg2.c_str(), msg2.size(), 5);
+  dw.closeFile();
+
+  readFile("file1.txt", buf, 15);
+  buf[15] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("1234567890ABCDE"), string(buf));
+  readFile("file2.txt", buf, 1);
+  buf[1] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("F"), string(buf));
+
+  dw.openFile(".");
+  string msg3 = "12345123456712";
+  dw.writeData(msg3.c_str(), msg3.size(), 10);
+  dw.closeFile();
+
+  readFile("file1.txt", buf, 15);
+  buf[15] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("123456789012345"), string(buf));
+  readFile("file2.txt", buf, 7);
+  buf[7] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("1234567"), string(buf));
+  readFile("file3.txt", buf, 2);
+  buf[2] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("12"), string(buf));
+}
+
+void MultiDiskWriterTest::testReadData() {
+  FileEntry entry1("file1r.txt", 15, 0);
+  FileEntry entry2("file2r.txt", 7, 15);
+  FileEntry entry3("file3r.txt", 3, 22);
+  MultiFileEntries entries;
+  entries.push_back(entry1);
+  entries.push_back(entry2);
+  entries.push_back(entry3);
+  MultiDiskWriter dw;
+  dw.setMultiFileEntries(entries, 2);
+
+  dw.openFile(".");
+  char buf[128];
+  dw.readData(buf, 15, 0);
+  buf[15] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("1234567890ABCDE"), string(buf));
+  dw.readData(buf, 10, 6);
+  buf[10] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("7890ABCDEF"), string(buf));
+  dw.readData(buf, 4, 20);
+  buf[4] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("KLMN"), string(buf));
+  dw.readData(buf, 25, 0);
+  buf[25] = '\0';
+  CPPUNIT_ASSERT_EQUAL(string("1234567890ABCDEFGHIJKLMNO"), string(buf));
+}
+
+void MultiDiskWriterTest::testSha1Sum() {
+  FileEntry entry1("file1r.txt", 15, 0);
+  FileEntry entry2("file2r.txt", 7, 15);
+  FileEntry entry3("file3r.txt", 3, 22);
+  MultiFileEntries entries;
+  entries.push_back(entry1);
+  entries.push_back(entry2);
+  entries.push_back(entry3);
+  MultiDiskWriter dw;
+  dw.setMultiFileEntries(entries, 2);
+
+  dw.openFile(".");
+  string sha1sum = dw.sha1Sum(0, 25);
+  CPPUNIT_ASSERT_EQUAL(string("76495faf71ca63df66dce99547d2c58da7266d9e"), sha1sum);
+  sha1sum = dw.sha1Sum(15, 7);
+  CPPUNIT_ASSERT_EQUAL(string("737660d816fb23c2d5bc74f62d9b01b852b2aaca"), sha1sum);
+  sha1sum = dw.sha1Sum(10, 14);
+  CPPUNIT_ASSERT_EQUAL(string("6238bf61dd8df8f77156b2378e9e39cd3939680c"), sha1sum);
+  dw.closeFile();
+}

+ 1 - 0
test/file1r.txt

@@ -0,0 +1 @@
+1234567890ABCDE

+ 1 - 0
test/file2r.txt

@@ -0,0 +1 @@
+FGHIJKL

+ 1 - 0
test/file3r.txt

@@ -0,0 +1 @@
+MNO