diff --git a/ChangeLog b/ChangeLog index 3e3204cc..21feecef 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,51 @@ +2006-04-12 Tatsuhiro Tsujikawa + + 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 To print ETA: diff --git a/TODO b/TODO index 8e7fed30..ccee211d 100644 --- a/TODO +++ b/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. \ No newline at end of file +* try to use ftruncate to allocate file. +* fix TorrentMan::getFilePath() \ No newline at end of file diff --git a/src/AbstractDiskWriter.cc b/src/AbstractDiskWriter.cc index 44b8b891..4e7cd642 100644 --- a/src/AbstractDiskWriter.cc +++ b/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); diff --git a/src/AbstractDiskWriter.h b/src/AbstractDiskWriter.h index 3f3c6f75..447aedaa 100644 --- a/src/AbstractDiskWriter.h +++ b/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); diff --git a/src/DiskWriter.h b/src/DiskWriter.h index d92419d3..9e910932 100644 --- a/src/DiskWriter.h +++ b/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_ diff --git a/src/Makefile.am b/src/Makefile.am index 4161700d..8b51cc10 100644 --- a/src/Makefile.am +++ b/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@\ diff --git a/src/Makefile.in b/src/Makefile.in index d5fcff57..a4330fba 100644 --- a/src/Makefile.in +++ b/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@ diff --git a/src/MultiDiskWriter.cc b/src/MultiDiskWriter.cc new file mode 100644 index 00000000..7172b809 --- /dev/null +++ b/src/MultiDiskWriter.cc @@ -0,0 +1,251 @@ +/* */ +#include "MultiDiskWriter.h" +#include "DlAbortEx.h" +#include "Util.h" +#include + +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 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 +} + diff --git a/src/MultiDiskWriter.h b/src/MultiDiskWriter.h new file mode 100644 index 00000000..fba08255 --- /dev/null +++ b/src/MultiDiskWriter.h @@ -0,0 +1,76 @@ +/* */ +#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 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_ diff --git a/src/PeerMessage.cc b/src/PeerMessage.cc index 65690ced..0c0583e4 100644 --- a/src/PeerMessage.cc +++ b/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"; diff --git a/src/TorrentConsoleDownloadEngine.cc b/src/TorrentConsoleDownloadEngine.cc index 8ae62910..7c3b68b6 100644 --- a/src/TorrentConsoleDownloadEngine.cc +++ b/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; diff --git a/src/TorrentConsoleDownloadEngine.h b/src/TorrentConsoleDownloadEngine.h index 0d177e14..26f0fc23 100644 --- a/src/TorrentConsoleDownloadEngine.h +++ b/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(); diff --git a/src/TorrentDownloadEngine.cc b/src/TorrentDownloadEngine.cc index c2c8c254..fe802bb9 100644 --- a/src/TorrentDownloadEngine.cc +++ b/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()); } } diff --git a/src/TorrentMan.cc b/src/TorrentMan.cc index 04e6cd11..c03b3762 100644 --- a/src/TorrentMan.cc +++ b/src/TorrentMan.cc @@ -30,6 +30,7 @@ #include "message.h" #include "PreAllocationDiskWriter.h" #include "DefaultDiskWriter.h" +#include "MultiDiskWriter.h" #include "prefs.h" #include #include @@ -379,17 +380,33 @@ void TorrentMan::setup(string metaInfoFile) { initBitfield(); delete topDic; +} - if(option->get(PREF_NO_PREALLOCATION) == V_TRUE) { - diskWriter = new DefaultDiskWriter(); +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 = new PreAllocationDiskWriter(totalLength); - } - if(segmentFileExists()) { - load(); - diskWriter->openExistingFile(getTempFilePath()); - } 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()); +} diff --git a/src/TorrentMan.h b/src/TorrentMan.h index 42affb9b..24d4422c 100644 --- a/src/TorrentMan.h +++ b/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 diff --git a/src/Util.cc b/src/Util.cc index d6328add..c7d36c1f 100644 --- a/src/Util.cc +++ b/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) { diff --git a/src/Util.h b/src/Util.h index e738c0ae..eead20b4 100644 --- a/src/Util.h +++ b/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. diff --git a/src/main.cc b/src/main.cc index af49abfb..5ee9011f 100644 --- a/src/main.cc +++ b/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); diff --git a/src/prefs.h b/src/prefs.h index dc17ea4f..e513a404 100644 --- a/src/prefs.h +++ b/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_ diff --git a/test/Makefile.am b/test/Makefile.am index d42fc342..035ee7eb 100644 --- a/test/Makefile.am +++ b/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} diff --git a/test/Makefile.in b/test/Makefile.in index 8b9804f3..b594bc86 100644 --- a/test/Makefile.in +++ b/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@ diff --git a/test/MultiDiskWriterTest.cc b/test/MultiDiskWriterTest.cc new file mode 100644 index 00000000..cc442975 --- /dev/null +++ b/test/MultiDiskWriterTest.cc @@ -0,0 +1,142 @@ +#include "MultiDiskWriter.h" +#include +#include + +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(); +} diff --git a/test/file1r.txt b/test/file1r.txt new file mode 100644 index 00000000..9531e9ff --- /dev/null +++ b/test/file1r.txt @@ -0,0 +1 @@ +1234567890ABCDE \ No newline at end of file diff --git a/test/file2r.txt b/test/file2r.txt new file mode 100644 index 00000000..ef9402ef --- /dev/null +++ b/test/file2r.txt @@ -0,0 +1 @@ +FGHIJKL \ No newline at end of file diff --git a/test/file3r.txt b/test/file3r.txt new file mode 100644 index 00000000..c25f2eb7 --- /dev/null +++ b/test/file3r.txt @@ -0,0 +1 @@ +MNO \ No newline at end of file