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;
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);
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;
};
.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
};
}
}
-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;
}
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... */
}
}
-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))
{
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)))
{
}
/* 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)
{
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;
}
****
***/
+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.
*
* @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
};
/* 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;
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);
**** 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)
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));
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);
}
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);
}
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
{
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)
{
#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)
return user_dir;
}
+char const* tr_getDefaultPieceSubDir(void)
+{
+ return PIECE_SUBDIR;
+}
+
/***
****
***/
/** @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*);
{ "pex-enabled", 11 },
{ "piece", 5 },
{ "piece length", 12 },
+ { "piece-temp-dir", 14 },
{ "pieceCount", 10 },
{ "pieceSize", 9 },
{ "pieces", 6 },
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,
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);
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;
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),
};
/**
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);
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;
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);
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));
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);
****
***/
+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));
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);
char* configDir;
char* resumeDir;
+ char* pieceDir;
char* torrentDir;
char* incompleteDir;
#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"
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);
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;
tr_cpDestruct(&tor->completion);
tr_free(tor->downloadDir);
+ tr_free(tor->pieceTempDir);
tr_free(tor->incompleteDir);
if (tor == session->torrentList)
}
}
+/**
+ * @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;
{
tr_metainfoRemoveSaved(tor->session, &tor->info);
tr_torrentRemoveResume(tor);
+ tr_torrentRemovePieceTemp(tor);
}
tor->isRunning = false;
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;
}
}
}
{
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);
}
/* 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 */
/* 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;
*/
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"
*/
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.
*/
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 */
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)
{
/* 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);
}
}