]> granicus.if.org Git - transmission/commitdiff
ijuxda's and cfpp2p's patch for TRAC-532 with some fixes trac532-collect-unwanted-blocks
authorMike Gelfand <mikedld@mikedld.com>
Tue, 23 May 2017 19:19:45 +0000 (22:19 +0300)
committerMike Gelfand <mikedld@mikedld.com>
Mon, 24 Jul 2017 21:34:27 +0000 (00:34 +0300)
18 files changed:
libtransmission/cache.c
libtransmission/cache.h
libtransmission/fdlimit.c
libtransmission/fdlimit.h
libtransmission/inout.c
libtransmission/platform.c
libtransmission/platform.h
libtransmission/quark.c
libtransmission/quark.h
libtransmission/resume.c
libtransmission/resume.h
libtransmission/rpcimpl.c
libtransmission/session.c
libtransmission/session.h
libtransmission/torrent.c
libtransmission/torrent.h
libtransmission/transmission.h
libtransmission/verify.c

index ab921637b4bd71d8e3adb8ba58c77e50b8a03a97..077aa4fc41b1aa2517d3579a00748f401287fb60 100644 (file)
@@ -450,6 +450,37 @@ int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t i)
     return err;
 }
 
+int tr_cacheFlushPiece(tr_cache* cache, tr_torrent* torrent, tr_piece_index_t i)
+{
+    int pos;
+    int err = 0;
+    tr_block_index_t first;
+    tr_block_index_t last;
+    tr_torGetPieceBlockRange(torrent, i, &first, &last);
+    pos = findBlockPos(cache, torrent, first);
+    dbgmsg("flushing piece %d from cache to disk: blocks [%zu...%zu]", (int)i, (size_t)first, (size_t)last);
+
+    /* flush out all the blocks in that piece */
+    while (err == 0 && pos < tr_ptrArraySize(&cache->blocks))
+    {
+        struct cache_block const* b = tr_ptrArrayNth(&cache->blocks, pos);
+
+        if (b->tor != torrent)
+        {
+            break;
+        }
+
+        if (b->block < first || b->block > last)
+        {
+            break;
+        }
+
+        err = flushContiguous(cache, pos, getBlockRun(cache, pos, NULL));
+    }
+
+    return err;
+}
+
 int tr_cacheFlushTorrent(tr_cache* cache, tr_torrent* torrent)
 {
     int err = 0;
index fe900bdc58340ed008bfb7ad9de44f9f922c337c..7a96b850c353f2d4f9942d06bd10d9d6d419d995 100644 (file)
@@ -48,4 +48,6 @@ int tr_cacheFlushDone(tr_cache* cache);
 
 int tr_cacheFlushTorrent(tr_cache* cache, tr_torrent* torrent);
 
+int tr_cacheFlushPiece(tr_cache* cache, tr_torrent* torrent, tr_piece_index_t piece);
+
 int tr_cacheFlushFile(tr_cache* cache, tr_torrent* torrent, tr_file_index_t file);
index 49c6a0c412ba8b199115ce8ed930014bbc8061ea..049edf2fcb2dcee10b9d305b7bfde8e91a236bfa 100644 (file)
@@ -124,7 +124,8 @@ struct tr_cached_file
     bool is_writable;
     tr_sys_file_t fd;
     int torrent_id;
-    tr_file_index_t file_index;
+    uint32_t index_num;
+    tr_fd_index_type index_type;
     time_t used_at;
 };
 
@@ -270,7 +271,8 @@ static void fileset_construct(struct tr_fileset* set, int n)
         .is_writable = false,
         .fd = TR_BAD_SYS_FILE,
         .torrent_id = 0,
-        .file_index = 0,
+        .index_num = 0,
+        .index_type = TR_FD_INDEX_FILE,
         .used_at = 0
     };
 
@@ -318,13 +320,13 @@ static void fileset_close_torrent(struct tr_fileset* set, int torrent_id)
     }
 }
 
-static struct tr_cached_file* fileset_lookup(struct tr_fileset* set, int torrent_id, tr_file_index_t i)
+static struct tr_cached_file* fileset_lookup(struct tr_fileset* set, int torrent_id, uint32_t i, tr_fd_index_type it)
 {
     if (set != NULL)
     {
         for (struct tr_cached_file* o = set->begin; o != set->end; ++o)
         {
-            if (torrent_id == o->torrent_id && i == o->file_index && cached_file_is_open(o))
+            if (torrent_id == o->torrent_id && i == o->index_num && it == o->index_type && cached_file_is_open(o))
             {
                 return o;
             }
@@ -439,11 +441,11 @@ static struct tr_fileset* get_fileset(tr_session* session)
     return &session->fdInfo->fileset;
 }
 
-void tr_fdFileClose(tr_session* s, tr_torrent const* tor, tr_file_index_t i)
+void tr_fdFileClose(tr_session* s, tr_torrent const* tor, uint32_t i, tr_fd_index_type it)
 {
     struct tr_cached_file* o;
 
-    if ((o = fileset_lookup(get_fileset(s), tr_torrentId(tor), i)) != NULL)
+    if ((o = fileset_lookup(get_fileset(s), tr_torrentId(tor), i, it)) != NULL)
     {
         /* flush writable files so that their mtimes will be
          * up-to-date when this function returns to the caller... */
@@ -456,9 +458,9 @@ void tr_fdFileClose(tr_session* s, tr_torrent const* tor, tr_file_index_t i)
     }
 }
 
-tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, tr_file_index_t i, bool writable)
+tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, uint32_t i, tr_fd_index_type it, bool writable)
 {
-    struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i);
+    struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i, it);
 
     if (o == NULL || (writable && !o->is_writable))
     {
@@ -469,11 +471,11 @@ tr_sys_file_t tr_fdFileGetCached(tr_session* s, int torrent_id, tr_file_index_t
     return o->fd;
 }
 
-bool tr_fdFileGetCachedMTime(tr_session* s, int torrent_id, tr_file_index_t i, time_t* mtime)
+bool tr_fdFileGetCachedMTime(tr_session* s, int torrent_id, uint32_t i, tr_fd_index_type it, time_t* mtime)
 {
     bool success;
     tr_sys_path_info info;
-    struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i);
+    struct tr_cached_file* o = fileset_lookup(get_fileset(s), torrent_id, i, it);
 
     if ((success = o != NULL && tr_sys_file_get_info(o->fd, &info, NULL)))
     {
@@ -491,11 +493,11 @@ void tr_fdTorrentClose(tr_session* session, int torrent_id)
 }
 
 /* returns an fd on success, or a TR_BAD_SYS_FILE on failure and sets errno */
-tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_index_t i, char const* filename, bool writable,
-    tr_preallocation_mode allocation, uint64_t file_size)
+tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, uint32_t i, tr_fd_index_type it, char const* filename,
+    bool writable, tr_preallocation_mode allocation, uint64_t file_size)
 {
     struct tr_fileset* set = get_fileset(session);
-    struct tr_cached_file* o = fileset_lookup(set, torrent_id, i);
+    struct tr_cached_file* o = fileset_lookup(set, torrent_id, i, it);
 
     if (o != NULL && writable && !o->is_writable)
     {
@@ -522,7 +524,8 @@ tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_ind
 
     dbgmsg("checking out '%s'", filename);
     o->torrent_id = torrent_id;
-    o->file_index = i;
+    o->index_num = i;
+    o->index_type = it;
     o->used_at = tr_time();
     return o->fd;
 }
index caa15b31fab87d95d69b8e8ac0262a0e201a536a..554c83d415164a8cb10b4164fe2965c076e5ddfe 100644 (file)
 ****
 ***/
 
+typedef enum
+{
+    TR_FD_INDEX_FILE,
+    TR_FD_INDEX_PIECE
+}
+tr_fd_index_type;
+
 /**
  * Returns an fd to the specified filename.
  *
  *
  * @see tr_fdFileClose
  */
-tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, tr_file_index_t file_num, char const* filename,
-    bool do_write, tr_preallocation_mode preallocation_mode, uint64_t preallocation_file_size);
+tr_sys_file_t tr_fdFileCheckout(tr_session* session, int torrent_id, uint32_t index_num, tr_fd_index_type index_type,
+    char const* filename, bool do_write, tr_preallocation_mode preallocation_mode, uint64_t preallocation_file_size);
 
-tr_sys_file_t tr_fdFileGetCached(tr_session* session, int torrent_id, tr_file_index_t file_num, bool doWrite);
+tr_sys_file_t tr_fdFileGetCached(tr_session* session, int torrent_id, uint32_t index_num, tr_fd_index_type index_type,
+    bool doWrite);
 
-bool tr_fdFileGetCachedMTime(tr_session* session, int torrent_id, tr_file_index_t file_num, time_t* mtime);
+bool tr_fdFileGetCachedMTime(tr_session* session, int torrent_id, uint32_t index_num, tr_fd_index_type index_type,
+    time_t* mtime);
 
 /**
  * Closes a file that's being held by our file repository.
@@ -53,7 +62,7 @@ bool tr_fdFileGetCachedMTime(tr_session* session, int torrent_id, tr_file_index_
  *
  * @see tr_fdFileCheckout
  */
-void tr_fdFileClose(tr_session* session, tr_torrent const* tor, tr_file_index_t file_num);
+void tr_fdFileClose(tr_session* session, tr_torrent const* tor, uint32_t index_num, tr_fd_index_type index_type);
 
 /**
  * Closes all the files associated with a given torrent id
index 78e8730a1563ab55d11c8dc4ed730f90377d451c..c40dd98fab5125295792febb37810f2fc0324e1c 100644 (file)
@@ -37,8 +37,8 @@ enum
 };
 
 /* returns 0 on success, or an errno on failure */
-static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr_file_index_t fileIndex, uint64_t fileOffset,
-    void* buf, size_t buflen)
+static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr_piece_index_t pieceIndex, uint32_t pieceOffset,
+    tr_file_index_t fileIndex, uint64_t fileOffset, void* buf, size_t buflen)
 {
     tr_sys_file_t fd;
     int err = 0;
@@ -46,6 +46,11 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr
     tr_info const* const info = &tor->info;
     tr_file const* const file = &info->files[fileIndex];
 
+    uint64_t offset;
+    uint64_t desiredSize;
+    uint32_t indexNum;
+    tr_fd_index_type indexType;
+
     TR_ASSERT(fileIndex < info->fileCount);
     TR_ASSERT(file->length == 0 || fileOffset < file->length);
     TR_ASSERT(fileOffset + buflen <= file->length);
@@ -59,27 +64,52 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr
     ****  Find the fd
     ***/
 
-    fd = tr_fdFileGetCached(session, tr_torrentId(tor), fileIndex, doWrite);
+    if (file->usept)
+    {
+        offset = pieceOffset;
+        desiredSize = tr_torPieceCountBytes(tor, pieceIndex);
+        indexNum = pieceIndex;
+        indexType = TR_FD_INDEX_PIECE;
+    }
+    else
+    {
+        offset = fileOffset;
+        desiredSize = file->length;
+        indexNum = fileIndex;
+        indexType = TR_FD_INDEX_FILE;
+    }
+
+    fd = tr_fdFileGetCached(session, tr_torrentId(tor), indexNum, indexType, doWrite);
 
     if (fd == TR_BAD_SYS_FILE)
     {
         /* it's not cached, so open/create it now */
         char* subpath;
         char const* base;
+        bool fileExists;
 
         /* see if the file exists... */
-        if (!tr_torrentFindFile2(tor, fileIndex, &base, &subpath, NULL))
+        if (file->usept)
         {
-            /* we can't read a file that doesn't exist... */
-            if (!doWrite)
+            fileExists = tr_torrentFindPieceTemp2(tor, pieceIndex, &base, &subpath);
+        }
+        else
+        {
+            fileExists = tr_torrentFindFile2(tor, fileIndex, &base, &subpath, NULL);
+
+            if (!fileExists)
             {
-                err = ENOENT;
+                /* figure out where the file should go, so we can create it */
+                base = tr_torrentGetCurrentDir(tor);
+                subpath = tr_sessionIsIncompleteFileNamingEnabled(tor->session) ? tr_torrentBuildPartial(tor, fileIndex) :
+                    tr_strdup(file->name);
             }
+        }
 
-            /* figure out where the file should go, so we can create it */
-            base = tr_torrentGetCurrentDir(tor);
-            subpath = tr_sessionIsIncompleteFileNamingEnabled(tor->session) ? tr_torrentBuildPartial(tor, fileIndex) :
-                tr_strdup(file->name);
+        /* we can't read a file that doesn't exist... */
+        if (!fileExists && !doWrite)
+        {
+            err = ENOENT;
         }
 
         if (err == 0)
@@ -88,8 +118,8 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr
             char* filename = tr_buildPath(base, subpath, NULL);
             int const prealloc = (file->dnd || !doWrite) ? TR_PREALLOCATE_NONE : tor->session->preallocationMode;
 
-            if ((fd = tr_fdFileCheckout(session, tor->uniqueId, fileIndex, filename, doWrite, prealloc,
-                    file->length)) == TR_BAD_SYS_FILE)
+            if ((fd = tr_fdFileCheckout(session, tor->uniqueId, indexNum, indexType, filename, doWrite, prealloc,
+                    desiredSize)) == TR_BAD_SYS_FILE)
             {
                 err = errno;
                 tr_logAddTorErr(tor, "tr_fdFileCheckout failed for \"%s\": %s", filename, tr_strerror(err));
@@ -116,7 +146,7 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr
 
         if (ioMode == TR_IO_READ)
         {
-            if (!tr_sys_file_read_at(fd, buf, buflen, fileOffset, NULL, &error))
+            if (!tr_sys_file_read_at(fd, buf, buflen, offset, NULL, &error))
             {
                 err = error->code;
                 tr_logAddTorErr(tor, "read failed for \"%s\": %s", file->name, error->message);
@@ -125,7 +155,7 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr
         }
         else if (ioMode == TR_IO_WRITE)
         {
-            if (!tr_sys_file_write_at(fd, buf, buflen, fileOffset, NULL, &error))
+            if (!tr_sys_file_write_at(fd, buf, buflen, offset, NULL, &error))
             {
                 err = error->code;
                 tr_logAddTorErr(tor, "write failed for \"%s\": %s", file->name, error->message);
@@ -134,7 +164,7 @@ static int readOrWriteBytes(tr_session* session, tr_torrent* tor, int ioMode, tr
         }
         else if (ioMode == TR_IO_PREFETCH)
         {
-            tr_sys_file_advise(fd, fileOffset, buflen, TR_SYS_FILE_ADVICE_WILL_NEED, NULL);
+            tr_sys_file_advise(fd, offset, buflen, TR_SYS_FILE_ADVICE_WILL_NEED, NULL);
         }
         else
         {
@@ -203,13 +233,33 @@ static int readOrWritePiece(tr_torrent* tor, int ioMode, tr_piece_index_t pieceI
     while (buflen != 0 && err == 0)
     {
         tr_file const* file = &info->files[fileIndex];
-        uint64_t const bytesThisPass = MIN(buflen, file->length - fileOffset);
+        uint32_t leftInPiece = tr_torPieceCountBytes(tor, pieceIndex) - pieceOffset;
+        uint64_t leftInFile = file->length - fileOffset;
+        uint64_t bytesThisPass;
+
+        bytesThisPass = MIN(leftInFile, leftInPiece);
+        bytesThisPass = MIN(bytesThisPass, buflen);
 
-        err = readOrWriteBytes(tor->session, tor, ioMode, fileIndex, fileOffset, buf, bytesThisPass);
+        err = readOrWriteBytes(tor->session, tor, ioMode, pieceIndex, pieceOffset, fileIndex, fileOffset, buf, bytesThisPass);
         buf += bytesThisPass;
         buflen -= bytesThisPass;
-        fileIndex++;
-        fileOffset = 0;
+
+        leftInPiece -= bytesThisPass;
+        leftInFile -= bytesThisPass;
+        pieceOffset += bytesThisPass;
+        fileOffset += bytesThisPass;
+
+        if (leftInPiece == 0)
+        {
+            ++pieceIndex;
+            pieceOffset = 0;
+        }
+
+        if (leftInFile == 0)
+        {
+            ++fileIndex;
+            fileOffset = 0;
+        }
 
         if (err != 0 && ioMode == TR_IO_WRITE && tor->error != TR_STAT_LOCAL_ERROR)
         {
index 4a8de7c4fab75a4c96ee3d7b01f416dfd75c12f8..aa054530b6ee57386b7e0a62e5e8c8f932582c82 100644 (file)
@@ -289,9 +289,11 @@ static char const* getHomeDir(void)
 #if defined(__APPLE__) || defined(_WIN32)
 #define RESUME_SUBDIR "Resume"
 #define TORRENT_SUBDIR "Torrents"
+#define PIECE_SUBDIR "Pieces"
 #else
 #define RESUME_SUBDIR "resume"
 #define TORRENT_SUBDIR "torrents"
+#define PIECE_SUBDIR "pieces"
 #endif
 
 void tr_setConfigDir(tr_session* session, char const* configDir)
@@ -459,6 +461,11 @@ char const* tr_getDefaultDownloadDir(void)
     return user_dir;
 }
 
+char const* tr_getDefaultPieceSubDir(void)
+{
+    return PIECE_SUBDIR;
+}
+
 /***
 ****
 ***/
index 802b76a222671c4b7de751f68fc477283ec6e018..4601af754f8d94a1762e8afddaa7dd8ecccff055 100644 (file)
@@ -31,6 +31,9 @@ void tr_setConfigDir(tr_session* session, char const* configDir);
 /** @brief return the directory where .resume files are stored */
 char const* tr_getResumeDir(tr_session const*);
 
+/** @brief return the default name of the directory where temporary piece files are stored */
+char const* tr_getDefaultPieceSubDir(void);
+
 /** @brief return the directory where .torrent files are stored */
 char const* tr_getTorrentDir(tr_session const*);
 
index 8610500576b21bb50b58618dc12c8cc0a84ae710..ae9002951e8760f36a7e557ed9369ccfef2dfce3 100644 (file)
@@ -239,6 +239,7 @@ static struct tr_key_struct const my_static[] =
     { "pex-enabled", 11 },
     { "piece", 5 },
     { "piece length", 12 },
+    { "piece-temp-dir", 14 },
     { "pieceCount", 10 },
     { "pieceSize", 9 },
     { "pieces", 6 },
index d40ab75fabd245b1b2bb0659990867c560fbb9ac..e0b4ec370b75680116983f58f09f7dcb1f6cea01 100644 (file)
@@ -241,6 +241,7 @@ enum
     TR_KEY_pex_enabled,
     TR_KEY_piece,
     TR_KEY_piece_length,
+    TR_KEY_piece_temp_dir,
     TR_KEY_pieceCount,
     TR_KEY_pieceSize,
     TR_KEY_pieces,
index 82b1f8f07d4f94e768e3d9a6db2aa73ebb6a8955..f4fbb5af994e89b795c570090e7bd6eea84b37ab 100644 (file)
@@ -716,6 +716,7 @@ void tr_torrentSaveResume(tr_torrent* tor)
         tr_variantDictAddStr(&top, TR_KEY_incomplete_dir, tor->incompleteDir);
     }
 
+    tr_variantDictAddStr(&top, TR_KEY_piece_temp_dir, tor->pieceTempDir);
     tr_variantDictAddInt(&top, TR_KEY_downloaded, tor->downloadedPrev + tor->downloadedCur);
     tr_variantDictAddInt(&top, TR_KEY_uploaded, tor->uploadedPrev + tor->uploadedCur);
     tr_variantDictAddInt(&top, TR_KEY_max_peers, tor->maxConnectedPeers);
@@ -811,6 +812,14 @@ static uint64_t loadFromFile(tr_torrent* tor, uint64_t fieldsToLoad)
         fieldsLoaded |= TR_FR_INCOMPLETE_DIR;
     }
 
+    if ((fieldsToLoad & TR_FR_PIECE_TEMP_DIR) != 0 && tr_variantDictFindStr(&top, TR_KEY_piece_temp_dir, &str, &len) &&
+        str != NULL && *str != '\0')
+    {
+        tr_free(tor->pieceTempDir);
+        tor->pieceTempDir = tr_strndup(str, len);
+        fieldsLoaded |= TR_FR_PIECE_TEMP_DIR;
+    }
+
     if ((fieldsToLoad & TR_FR_DOWNLOADED) != 0 && tr_variantDictFindInt(&top, TR_KEY_downloaded, &i))
     {
         tor->downloadedPrev = i;
index 513c1b5f7c6e9640e4b0053aedd06975253787a7..e619568c5c116aa02e9805c2c4819068e345a565 100644 (file)
@@ -35,7 +35,8 @@ enum
     TR_FR_TIME_SEEDING = (1 << 18),
     TR_FR_TIME_DOWNLOADING = (1 << 19),
     TR_FR_FILENAMES = (1 << 20),
-    TR_FR_NAME = (1 << 21)
+    TR_FR_NAME = (1 << 21),
+    TR_FR_PIECE_TEMP_DIR = (1 << 22),
 };
 
 /**
index e3896f197f53fa56fc30a5c532ff75eef3d032fe..02b15c6bc3de2fe040ad6db97951e1fe692a97e7 100644 (file)
@@ -1919,6 +1919,11 @@ static char const* sessionSet(tr_session* session, tr_variant* args_in, tr_varia
         tr_sessionSetDownloadDir(session, download_dir);
     }
 
+    if (tr_variantDictFindStr(args_in, TR_KEY_piece_temp_dir, &str, NULL))
+    {
+        tr_sessionSetPieceTempDir(session, str);
+    }
+
     if (tr_variantDictFindInt(args_in, TR_KEY_queue_stalled_minutes, &i))
     {
         tr_sessionSetQueueStalledMinutes(session, i);
@@ -2206,6 +2211,10 @@ static void addSessionField(tr_session* s, tr_variant* d, tr_quark key)
         tr_variantDictAddInt(d, key, tr_sessionGetQueueSize(s, TR_DOWN));
         break;
 
+    case TR_KEY_piece_temp_dir:
+        tr_variantDictAddStr(d, key, tr_sessionGetPieceTempDir(s));
+        break;
+
     case TR_KEY_peer_limit_global:
         tr_variantDictAddInt(d, key, tr_sessionGetPeerLimit(s));
         break;
index 86d054f7fd594c9765e899bddecc49c84d42ea18..de5574c7ca8d1bfada307b80ddd2125d9bad232d 100644 (file)
@@ -337,6 +337,7 @@ void tr_sessionGetDefaultSettings(tr_variant* d)
     tr_variantDictAddBool(d, TR_KEY_utp_enabled, true);
     tr_variantDictAddBool(d, TR_KEY_lpd_enabled, false);
     tr_variantDictAddStr(d, TR_KEY_download_dir, tr_getDefaultDownloadDir());
+    tr_variantDictAddStr(d, TR_KEY_piece_temp_dir, "");
     tr_variantDictAddInt(d, TR_KEY_speed_limit_down, 100);
     tr_variantDictAddBool(d, TR_KEY_speed_limit_down_enabled, false);
     tr_variantDictAddInt(d, TR_KEY_encryption, TR_DEFAULT_ENCRYPTION);
@@ -407,6 +408,7 @@ void tr_sessionGetSettings(tr_session* s, tr_variant* d)
     tr_variantDictAddBool(d, TR_KEY_utp_enabled, s->isUTPEnabled);
     tr_variantDictAddBool(d, TR_KEY_lpd_enabled, s->isLPDEnabled);
     tr_variantDictAddStr(d, TR_KEY_download_dir, tr_sessionGetDownloadDir(s));
+    tr_variantDictAddStr(d, TR_KEY_piece_temp_dir, tr_sessionGetPieceTempDir(s));
     tr_variantDictAddInt(d, TR_KEY_download_queue_size, tr_sessionGetQueueSize(s, TR_DOWN));
     tr_variantDictAddBool(d, TR_KEY_download_queue_enabled, tr_sessionGetQueueEnabled(s, TR_DOWN));
     tr_variantDictAddInt(d, TR_KEY_speed_limit_down, tr_sessionGetSpeedLimit_KBps(s, TR_DOWN));
@@ -939,6 +941,11 @@ static void sessionSetImpl(void* vdata)
         tr_sessionSetDownloadDir(session, str);
     }
 
+    if (tr_variantDictFindStr(settings, TR_KEY_piece_temp_dir, &str, NULL))
+    {
+        tr_sessionSetPieceTempDir(session, str);
+    }
+
     if (tr_variantDictFindStr(settings, TR_KEY_incomplete_dir, &str, NULL))
     {
         tr_sessionSetIncompleteDir(session, str);
@@ -1200,6 +1207,34 @@ int64_t tr_sessionGetDirFreeSpace(tr_session* session, char const* dir)
 ****
 ***/
 
+char const* tr_sessionGetPieceTempDir(tr_session const* session)
+{
+    assert(tr_isSession(session));
+    return session->pieceDir;
+}
+
+void tr_sessionSetPieceTempDir(tr_session* session, char const* path)
+{
+    assert(tr_isSession(session));
+    tr_sessionLock(session);
+    tr_free(session->pieceDir);
+
+    if (path != NULL && *path != '\0')
+    {
+        session->pieceDir = tr_strdup(path);
+    }
+    else
+    {
+        session->pieceDir = tr_buildPath(tr_sessionGetConfigDir(session), tr_getDefaultPieceSubDir(), NULL);
+    }
+
+    tr_sessionUnlock(session);
+}
+
+/***
+****
+***/
+
 void tr_sessionSetIncompleteFileNamingEnabled(tr_session* session, bool b)
 {
     TR_ASSERT(tr_isSession(session));
@@ -2098,6 +2133,7 @@ void tr_sessionClose(tr_session* session)
     tr_free(session->torrentDoneScript);
     tr_free(session->configDir);
     tr_free(session->resumeDir);
+    tr_free(session->pieceDir);
     tr_free(session->torrentDir);
     tr_free(session->incompleteDir);
     tr_free(session->blocklist_url);
index 9499507f9842f2210fecdf72e06c0604c89e0f07..da3fa65bfe13d864acb6f06b03accdad393a2096 100644 (file)
@@ -181,6 +181,7 @@ struct tr_session
 
     char* configDir;
     char* resumeDir;
+    char* pieceDir;
     char* torrentDir;
     char* incompleteDir;
 
index 4f7e75cbe199da72096ce67537a8db3add10746b..94826b17eb45b6fa38014ea04bbb75b9fa7de908 100644 (file)
@@ -34,6 +34,7 @@
 #include "fdlimit.h" /* tr_fdTorrentClose */
 #include "file.h"
 #include "inout.h" /* tr_ioTestPiece() */
+#include "list.h"
 #include "log.h"
 #include "magnet.h"
 #include "metainfo.h"
@@ -954,6 +955,12 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
         tor->incompleteDir = tr_strdup(dir);
     }
 
+    {
+        char* s = tr_metainfoGetBasename(&tor->info);
+        tor->pieceTempDir = tr_buildPath(tr_sessionGetPieceTempDir(tor->session), s, NULL);
+        tr_free(s);
+    }
+
     tr_bandwidthConstruct(&tor->bandwidth, session, &session->bandwidth);
 
     tor->bandwidth.priority = tr_ctorGetBandwidthPriority(ctor);
@@ -976,6 +983,7 @@ static void torrentInit(tr_torrent* tor, tr_ctor const* ctor)
     tr_ctorInitTorrentWanted(ctor, tor);
 
     refreshCurrentDir(tor);
+    tr_sys_dir_create(tor->pieceTempDir, TR_SYS_DIR_CREATE_PARENTS, 0777, NULL);
 
     doStart = tor->isRunning;
     tor->isRunning = false;
@@ -1692,6 +1700,7 @@ static void freeTorrent(tr_torrent* tor)
     tr_cpDestruct(&tor->completion);
 
     tr_free(tor->downloadDir);
+    tr_free(tor->pieceTempDir);
     tr_free(tor->incompleteDir);
 
     if (tor == session->torrentList)
@@ -2049,6 +2058,39 @@ void tr_torrentStop(tr_torrent* tor)
     }
 }
 
+/**
+ * @brief Delete all temporary piece files for the torrent.
+ */
+static void tr_torrentRemovePieceTemp(tr_torrent* tor)
+{
+    tr_sys_dir_t dir;
+    tr_list* files = NULL;
+    char const* path = tor->pieceTempDir;
+
+    if ((dir = tr_sys_dir_open(path, NULL)) != TR_BAD_SYS_DIR)
+    {
+        char const* name;
+
+        while ((name = tr_sys_dir_read_name(dir, NULL)) != NULL)
+        {
+            if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0)
+            {
+                tr_list_append(&files, tr_buildPath(path, name, NULL));
+            }
+        }
+
+        tr_sys_dir_close(dir, NULL);
+        tr_list_append(&files, tr_strdup(path));
+    }
+
+    for (tr_list* l = files; l != NULL; l = l->next)
+    {
+        tr_sys_path_remove(l->data, NULL);
+    }
+
+    tr_list_free(&files, tr_free);
+}
+
 static void closeTorrent(void* vtor)
 {
     tr_torrent* tor = vtor;
@@ -2068,6 +2110,7 @@ static void closeTorrent(void* vtor)
     {
         tr_metainfoRemoveSaved(tor->session, &tor->info);
         tr_torrentRemoveResume(tor);
+        tr_torrentRemovePieceTemp(tor);
     }
 
     tor->isRunning = false;
@@ -2491,71 +2534,363 @@ tr_priority_t* tr_torrentGetFilePriorities(tr_torrent const* tor)
     return p;
 }
 
+/***
+****
+***/
+
+bool tr_torrentFindPieceTemp2(tr_torrent const* tor, tr_piece_index_t pieceIndex, char const** base, char** subpath)
+{
+    char const* b = tor->pieceTempDir;
+    char* s;
+    char* filename;
+    bool exists;
+
+    s = tr_strdup_printf("%010u.dat", pieceIndex);
+
+    filename = tr_buildPath(b, s, NULL);
+    exists = tr_sys_path_exists(filename, NULL);
+    tr_free(filename);
+
+    if (base != NULL)
+    {
+        *base = b;
+    }
+
+    if (subpath != NULL)
+    {
+        *subpath = s;
+    }
+    else
+    {
+        tr_free(s);
+    }
+
+    return exists;
+}
+
+char* tr_torrentFindPieceTemp(tr_torrent const* tor, tr_piece_index_t pieceIndex)
+{
+    char const* base;
+    char* subpath;
+    char* filename = NULL;
+
+    if (tr_torrentFindPieceTemp2(tor, pieceIndex, &base, &subpath))
+    {
+        filename = tr_buildPath(base, subpath, NULL);
+        tr_free(subpath);
+    }
+
+    return filename;
+}
+
 /**
 ***  File DND
 **/
 
+static void removePieceTemp(tr_torrent* tor, tr_piece_index_t piece)
+{
+    char* filename;
+
+    tr_fdFileClose(tor->session, tor, piece, TR_FD_INDEX_PIECE);
+
+    if ((filename = tr_torrentFindPieceTemp(tor, piece)) != NULL)
+    {
+        tr_sys_path_remove(filename, NULL);
+        tr_free(filename);
+    }
+}
+
+/**
+ * @return TRUE if the file should use temporary piece files.
+ */
+static bool usePieceTemp(tr_torrent* tor, tr_file_index_t i)
+{
+    if (!tor->info.files[i].dnd)
+    {
+        return false;
+    }
+
+    tr_cacheFlushFile(tor->session->cache, tor, i);
+
+    return !tr_torrentFindFile2(tor, i, NULL, NULL, NULL);
+}
+
+static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t fileIndex);
+
+/**
+ * @note This function assumes @a tor is valid and already locked, and
+ *       @a file_index is a valid file index for the torrent.
+ * @note When @a file->dnd is TRUE and @a dnd is false, this function has
+ *       the side effect of copying over data from temporary piece files
+ *       to the destination file.
+ * @see readOrWriteBytes()
+ */
 static void setFileDND(tr_torrent* tor, tr_file_index_t fileIndex, int doDownload)
 {
     int8_t const dnd = !doDownload;
-    tr_piece_index_t firstPiece;
-    int8_t firstPieceDND;
-    tr_piece_index_t lastPiece;
-    int8_t lastPieceDND;
     tr_file* file = &tor->info.files[fileIndex];
 
-    file->dnd = dnd;
-    firstPiece = file->firstPiece;
-    lastPiece = file->lastPiece;
+    if (file->dnd == dnd)
+       {
+        return;
+    }
+
+    tr_piece_index_t const fpindex = file->firstPiece;
+    tr_piece_index_t const lpindex = file->lastPiece;
+    bool fpmovept;
+    bool lpmovept;
+    uint64_t fpoverlap;
+    uint64_t lpoverlap;
+    uint64_t fpoffset;
+    uint64_t lpoffset;
+    uint8_t* fpbuf;
+    uint8_t* lpbuf;
+
+    /* Flags indicating whether we need to copy over existing data
+    * from temporary piece files to the actual destination file. */
+    fpmovept = file->usept && doDownload;
+    lpmovept = fpmovept && fpindex != lpindex;
+
+    /* Check cache and filesystem to make sure temporary piece files exist. */
+
+    if (fpmovept)
+    {
+        tr_cacheFlushPiece(tor->session->cache, tor, fpindex);
+           fpmovept = tr_torrentFindPieceTemp2(tor, fpindex, NULL, NULL);
+    }
+
+    if (lpmovept)
+    {
+        tr_cacheFlushPiece(tor->session->cache, tor, lpindex);
+        lpmovept = tr_torrentFindPieceTemp2(tor, lpindex, NULL, NULL);
+    }
 
-    /* can't set the first piece to DND unless
-       every file using that piece is DND */
-    firstPieceDND = dnd;
+    bool const rwfpmovept = fpmovept;
+    bool const rwlpmovept = lpmovept;
 
-    if (fileIndex > 0)
+    if (fpmovept)
     {
-        for (tr_file_index_t i = fileIndex - 1; firstPieceDND; --i)
+        char* filename = tr_torrentFindPieceTemp(tor, fpindex);
+        tr_sys_file_t const fdpt = filename == NULL ? TR_BAD_SYS_FILE : tr_sys_file_open(filename,
+            TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0, NULL);
+        tr_free(filename);
+
+        if (fdpt != TR_BAD_SYS_FILE)
         {
-            if (tor->info.files[i].lastPiece != firstPiece)
+            fpoffset = file->offset - tr_pieceOffset(tor, fpindex, 0, 0);
+            fpoverlap = tr_torPieceCountBytes(tor, fpindex) - fpoffset;
+
+            if (fpoverlap > file->length)
             {
-                break;
+                fpoverlap = file->length;
             }
 
-            firstPieceDND = tor->info.files[i].dnd;
+            fpbuf = tr_malloc0(fpoverlap);
 
-            if (i == 0)
+            if (!tr_sys_file_read_at(fdpt, fpbuf, fpoverlap, fpoffset, NULL, NULL))
             {
-                break;
+                tr_free(fpbuf);
+                fpmovept = false;
+            }
+
+            tr_sys_file_close(fdpt, NULL);
+        }
+        else
+        {
+            fpmovept = false;
+        }
+    }
+
+    if (lpmovept && rwfpmovept == fpmovept)
+    {
+        char* filename = tr_torrentFindPieceTemp(tor, lpindex);
+        tr_sys_file_t const fdpt = filename == NULL ? TR_BAD_SYS_FILE : tr_sys_file_open(filename,
+            TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0, NULL);
+        tr_free(filename);
+
+        if (fdpt != TR_BAD_SYS_FILE)
+        {
+            lpoffset = 0;
+            lpoverlap = file->offset + file->length - tr_pieceOffset(tor, lpindex, 0, 0);
+            lpbuf = tr_malloc0(lpoverlap);
+
+            if (!tr_sys_file_read_at(fdpt, lpbuf, lpoverlap, lpoffset, NULL, NULL))
+            {
+                tr_free (lpbuf);
+                lpmovept = false;
+
+                if (fpmovept)
+                {
+                    tr_free(fpbuf);
+                }
+            }
+
+            tr_sys_file_close(fdpt, NULL);
+        }
+        else
+        {
+            lpmovept = false;
+
+            if (fpmovept)
+            {
+                tr_free(fpbuf);
             }
         }
     }
 
-    /* can't set the last piece to DND unless
-       every file using that piece is DND */
-    lastPieceDND = dnd;
+    uint8_t const svdnd = file->dnd;
+    bool const svusept = file->usept;
 
-    for (tr_file_index_t i = fileIndex + 1; lastPieceDND && i < tor->info.fileCount; ++i)
+    file->dnd = dnd;
+
+    if (fpmovept || lpmovept)
+    {
+        file->usept = false;
+    }
+    else
     {
-        if (tor->info.files[i].firstPiece != lastPiece)
+        file->usept = usePieceTemp(tor, fileIndex);
+    }
+
+    if (fpmovept && rwlpmovept == lpmovept)
+    {
+        int const nr = tr_ioWrite(tor, fpindex, fpoffset, fpoverlap, fpbuf);
+
+        if (nr != 0)
         {
-            break;
+            fpmovept = false;
+
+            if (lpmovept)
+            {
+                tr_free(lpbuf);
+            }
         }
 
-        lastPieceDND = tor->info.files[i].dnd;
+        tr_free(fpbuf);
     }
 
-    if (firstPiece == lastPiece)
+    if (lpmovept && rwfpmovept == fpmovept)
     {
-        tor->info.pieces[firstPiece].dnd = firstPieceDND && lastPieceDND;
+        int const nr = tr_ioWrite(tor, lpindex, lpoffset, lpoverlap, lpbuf);
+
+        if (nr != 0)
+        {
+            lpmovept = false;
+        }
+
+        tr_free(lpbuf);
+    }
+
+    /* Check conditions for setting piece DND and
+     * removing temporary piece files:
+     * - We can set the piece to DND if all files using
+     *   that piece are DND.
+     * - We can remove the temporary piece file if all
+     *   files using it have 'usept' set to false.
+     * - Do not delete temporary piece files if write failed. */
+
+    /* Do not change DND state if piece temp copy failed */
+
+    if (rwfpmovept == fpmovept && rwlpmovept == lpmovept)
+    {
+        tr_file_index_t i;
+
+        bool fpdnd = file->dnd;
+        bool fpnopt = !file->usept;
+
+        if (fileIndex > 0)
+        {
+            for (i = fileIndex - 1; fpdnd || fpnopt; --i)
+            {
+                if (tor->info.files[i].lastPiece != fpindex)
+                {
+                    break;
+                }
+
+                if (fpdnd)
+                {
+                    fpdnd = tor->info.files[i].dnd;
+                }
+
+                if (fpnopt)
+                {
+                    fpnopt = !tor->info.files[i].usept;
+                }
+
+                if (i == 0)
+                {
+                    break;
+                }
+            }
+        }
+
+        bool lpdnd = file->dnd;
+        bool lpnopt = !file->usept;
+
+        for (i = fileIndex + 1; (lpdnd || lpnopt) && i < tor->info.fileCount; ++i)
+        {
+            if (tor->info.files[i].firstPiece != lpindex)
+            {
+                break;
+            }
+
+            if (lpdnd)
+            {
+                lpdnd = tor->info.files[i].dnd;
+            }
+
+            if (lpnopt)
+            {
+                lpnopt = !tor->info.files[i].usept;
+            }
+        }
+
+        if (fpindex == lpindex)
+        {
+            tor->info.pieces[fpindex].dnd = fpdnd && lpdnd;
+
+            if (fpnopt && lpnopt)
+            {
+                removePieceTemp(tor, fpindex);
+            }
+        }
+        else
+        {
+            tor->info.pieces[fpindex].dnd = fpdnd;
+            tor->info.pieces[lpindex].dnd = lpdnd;
+
+            for (tr_piece_index_t p = fpindex + 1; p < lpindex; ++p)
+            {
+                tor->info.pieces[p].dnd = dnd;
+            }
+
+            if (fpnopt)
+            {
+                removePieceTemp(tor, fpindex);
+            }
+
+            if (lpnopt)
+            {
+                removePieceTemp(tor, lpindex);
+            }
+        }
+
+        if (tr_cpFileIsComplete(&tor->completion, fileIndex))
+        {
+            tr_torrentFileCompleted(tor, fileIndex);
+        }
     }
     else
     {
-        tor->info.pieces[firstPiece].dnd = firstPieceDND;
-        tor->info.pieces[lastPiece].dnd = lastPieceDND;
+        file->dnd = svdnd;
 
-        for (tr_piece_index_t pp = firstPiece + 1; pp < lastPiece; ++pp)
+        if (rwfpmovept || rwlpmovept)
+        {
+            file->usept = svusept;
+        }
+        else
         {
-            tor->info.pieces[pp].dnd = dnd;
+            file->usept = svusept;
         }
     }
 }
@@ -2783,7 +3118,7 @@ time_t tr_torrentGetFileMTime(tr_torrent const* tor, tr_file_index_t i)
 {
     time_t mtime = 0;
 
-    if (!tr_fdFileGetCachedMTime(tor->session, tor->uniqueId, i, &mtime))
+    if (!tr_fdFileGetCachedMTime(tor->session, tor->uniqueId, i, TR_FD_INDEX_FILE, &mtime))
     {
         tr_torrentFindFile2(tor, i, NULL, NULL, &mtime);
     }
@@ -3418,7 +3753,7 @@ static void tr_torrentFileCompleted(tr_torrent* tor, tr_file_index_t fileIndex)
 
     /* close the file so that we can reopen in read-only mode as needed */
     tr_cacheFlushFile(tor->session->cache, tor, fileIndex);
-    tr_fdFileClose(tor->session, tor, fileIndex);
+    tr_fdFileClose(tor->session, tor, fileIndex, TR_FD_INDEX_FILE);
 
     /* now that the file is complete and closed, we can start watching its
      * mtime timestamp for changes to know if we need to reverify pieces */
index 52c2efcf1f717c6f090b7d28ebc3363c38597e80..f4940b25262d7d4594d6d87cf5575514d63c107f 100644 (file)
@@ -134,6 +134,9 @@ struct tr_torrent
     /* Where the files are when the torrent is incomplete */
     char* incompleteDir;
 
+    /* Where temporary piece files are stored. */
+    char* pieceTempDir;
+
     /* Length, in bytes, of the "info" dict in the .torrent file. */
     size_t infoDictLength;
 
@@ -360,6 +363,26 @@ void tr_torrentGotBlock(tr_torrent* tor, tr_block_index_t blockIndex);
  */
 bool tr_torrentFindFile2(tr_torrent const*, tr_file_index_t fileNo, char const** base, char** subpath, time_t* mtime);
 
+/**
+ * Like tr_torrentFindFile2() but for temporary piece files.
+ * Both @a base and @a subpath may be NULL.
+ *
+ * @see tr_torrentFindFile2()
+ * @see tr_torrentFilePieceTemp()
+ */
+bool tr_torrentFindPieceTemp2(tr_torrent const* tor, tr_piece_index_t pieceIndex, char const** base, char** subpath);
+
+/**
+ * Get the full path of the temporary piece file for piece
+ * with index @a pieceIndex.
+ *
+ * @return a newly allocated string containing the full filename
+ *         or NULL if it does not exist.
+ *
+ * @see tr_torrentFindPieceTemp2()
+ */
+char* tr_torrentFindPieceTemp(tr_torrent const* tor, tr_piece_index_t pieceIndex);
+
 /* Returns a newly-allocated version of the tr_file.name string
  * that's been modified to denote that it's not a complete file yet.
  * In the current implementation this is done by appending ".part"
index ac1871adbd023368518f6be9f5561417e9e24370..6ec84b1a55d38da490ffdfdd383dd45f9504c46c 100644 (file)
@@ -247,6 +247,23 @@ char const* tr_sessionGetDownloadDir(tr_session const* session);
  */
 int64_t tr_sessionGetDirFreeSpace(tr_session* session, char const* dir);
 
+/**
+* @brief Get the full path to where temporary piece files are stored.
+ */
+char const* tr_sessionGetPieceTempDir(tr_session const* session);
+
+/**
+ * @brief Set the full path to where temporary piece files are stored.
+ * @param path If empty or NULL a default path consisting of the
+ *             config directory and a "pieces" sub-directory is set.
+ * @note Changing this setting will only affect new torrents. Existing
+ *       torrents will continue to use the same directory as was set
+ *       when they were created.
+ * @see tr_sessionGetConfigDir
+ * @see tr_getDefaultPieceSubDir
+ */
+void tr_sessionSetPieceTempDir(tr_session* session, char const* path);
+
 /**
  * @brief Set the torrent's bandwidth priority.
  */
@@ -1573,6 +1590,7 @@ typedef struct tr_file
     int8_t priority; /* TR_PRI_HIGH, _NORMAL, or _LOW */
     int8_t dnd; /* "do not download" flag */
     int8_t is_renamed; /* true if we're using a different path from the one in the metainfo; ie, if the user has renamed it */
+    int8_t usept; /* nonzero if using temporary piece files */
     tr_piece_index_t firstPiece; /* We need pieces [firstPiece... */
     tr_piece_index_t lastPiece; /* ...lastPiece] to dl this file */
     uint64_t offset; /* file begins at the torrent's nth byte */
index b4f0d605549268af9ab33faaf63431362e6fd613..d4e524a8d0fc6695a88cbeb5f7ab8ed8204bb9ec 100644 (file)
@@ -65,6 +65,21 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
             hadPiece = tr_torrentPieceIsComplete(tor, pieceIndex);
         }
 
+        /* check for temporary piece files */
+        if (file->usept && (piecePos == 0 || filePos == 0) && fd == TR_BAD_SYS_FILE &&
+            (pieceIndex == file->firstPiece || pieceIndex == file->lastPiece))
+        {
+            char* filename = tr_torrentFindPieceTemp(tor, pieceIndex);
+            fd = filename == NULL ? TR_BAD_SYS_FILE : tr_sys_file_open(filename, TR_SYS_FILE_READ | TR_SYS_FILE_SEQUENTIAL, 0,
+                NULL);
+            tr_free(filename);
+
+            if (filePos == 0 && fileIndex != prevFileIndex)
+            {
+                prevFileIndex = fileIndex;
+            }
+        }
+
         /* if we're starting a new file... */
         if (filePos == 0 && fd == TR_BAD_SYS_FILE && fileIndex != prevFileIndex)
         {
@@ -84,13 +99,14 @@ static bool verifyTorrent(tr_torrent* tor, bool* stopFlag)
         /* read a bit */
         if (fd != TR_BAD_SYS_FILE)
         {
+            uint64_t const readPos = file->usept ? piecePos : filePos;
             uint64_t numRead;
 
-            if (tr_sys_file_read_at(fd, buffer, bytesThisPass, filePos, &numRead, NULL) && numRead > 0)
+            if (tr_sys_file_read_at(fd, buffer, bytesThisPass, readPos, &numRead, NULL) && numRead > 0)
             {
                 bytesThisPass = numRead;
                 tr_sha1_update(sha, buffer, bytesThisPass);
-                tr_sys_file_advise(fd, filePos, bytesThisPass, TR_SYS_FILE_ADVICE_DONT_NEED, NULL);
+                tr_sys_file_advise(fd, readPos, bytesThisPass, TR_SYS_FILE_ADVICE_DONT_NEED, NULL);
             }
         }