1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 09:37:56 +00:00

perf: use scrape to know when a private swarm is all-seeds (#1780)

* perf: use scrape to know when a swarm is all-seeds

For private torrents, the tracker is the sole source of peers. So when a
private torrent's tracker responds that there are 0 leechers, we can use
that information to mark the entire swarm as seeders and to not initiate
connections to those peers if we are seeding. This can help seedboxes to
more efficiently pick which swarms to prioritize.

This strategy is not used on public torrents, since new seeder-to-seeder
connections can be useful there for pex.

This PR changes tr_peerMgrAddPex() to (1) remove tr_atom.seedProbability
field (which was not as robust as intended) and (2) add batches of peers
instead of a single peer.

* fix: only use all-seeds check for private torrents
This commit is contained in:
Charles Kerr 2021-09-09 15:25:30 -05:00 committed by GitHub
parent f83e07e2a0
commit d6cb99e57c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 148 additions and 182 deletions

View file

@ -504,14 +504,13 @@ static tr_tier* getTier(tr_announcer* announcer, uint8_t const* info_hash, int t
**** PUBLISH
***/
static tr_tracker_event const TRACKER_EVENT_INIT = {
.messageType = TR_TRACKER_WARNING,
.text = NULL,
.tracker = NULL,
.pex = NULL,
.pexCount = 0,
.seedProbability = 0,
};
static tr_tracker_event const TRACKER_EVENT_INIT = { .messageType = TR_TRACKER_WARNING,
.text = NULL,
.tracker = NULL,
.pex = NULL,
.pexCount = 0,
.seeders = 0,
.leechers = 0 };
static void publishMessage(tr_tier* tier, char const* msg, int type)
{
@ -546,34 +545,31 @@ static void publishError(tr_tier* tier, char const* msg)
publishMessage(tier, msg, TR_TRACKER_ERROR);
}
static int8_t getSeedProbability(tr_tier const* tier, int seeds, int leechers, int pex_count)
static void publishPeerCounts(tr_tier* tier, int seeders, int leechers)
{
/* special case optimization:
ocelot omits seeds from peer lists sent to seeds on private trackers.
so check for that case... */
if (leechers == pex_count && tr_torrentIsPrivate(tier->tor) && tr_torrentIsSeed(tier->tor) && seeds + leechers < NUMWANT)
if (tier->tor->tiers->callback != NULL)
{
return 0;
}
tr_tracker_event e = TRACKER_EVENT_INIT;
e.messageType = TR_TRACKER_COUNTS;
e.seeders = seeders;
e.leechers = leechers;
dbgmsg(tier, "peer counts: %d seeders, %d leechers.", seeders, leechers);
if (seeds >= 0 && leechers >= 0 && seeds + leechers > 0)
{
return (int8_t)(100.0 * seeds / (seeds + leechers));
(*tier->tor->tiers->callback)(tier->tor, &e, NULL);
}
return -1; /* unknown */
}
static void publishPeersPex(tr_tier* tier, int seeds, int leechers, tr_pex const* pex, int n)
static void publishPeersPex(tr_tier* tier, int seeders, int leechers, tr_pex const* pex, int n)
{
if (tier->tor->tiers->callback != NULL)
{
tr_tracker_event e = TRACKER_EVENT_INIT;
e.messageType = TR_TRACKER_PEERS;
e.seedProbability = getSeedProbability(tier, seeds, leechers, n);
e.seeders = seeders;
e.leechers = leechers;
e.pex = pex;
e.pexCount = n;
dbgmsg(tier, "got %d peers; seed prob %d", n, (int)e.seedProbability);
dbgmsg(tier, "tracker knows of %d seeders and %d leechers and gave a list of %d peers.", seeders, leechers, n);
(*tier->tor->tiers->callback)(tier->tor, &e, NULL);
}
@ -1225,6 +1221,8 @@ static void on_announce_done(tr_announce_response const* response, void* vdata)
publishPeersPex(tier, seeders, leechers, response->pex6, response->pex6_count);
}
publishPeerCounts(tier, seeders, leechers);
tier->isRunning = data->isRunningOnSuccess;
/* if the tracker included scrape fields in its announce response,
@ -1496,6 +1494,11 @@ static void on_scrape_done(tr_scrape_response const* response, void* vsession)
tracker->downloaderCount = row->downloaders;
tracker->consecutiveFailures = 0;
}
if (row->seeders >= 0 && row->leechers >= 0 && row->downloads >= 0)
{
publishPeerCounts(tier, row->seeders, row->leechers);
}
}
}
}

View file

@ -26,7 +26,8 @@ typedef enum
TR_TRACKER_WARNING,
TR_TRACKER_ERROR,
TR_TRACKER_ERROR_CLEAR,
TR_TRACKER_PEERS
TR_TRACKER_PEERS,
TR_TRACKER_COUNTS,
} TrackerEventType;
struct tr_pex;
@ -45,8 +46,9 @@ typedef struct
struct tr_pex const* pex;
size_t pexCount;
/* [0...100] for probability a peer is a seed. calculated by the leecher/seeder ratio */
int8_t seedProbability;
/* for TR_TRACKER_PEERS and TR_TRACKER_COUNTS */
int leechers;
int seeders;
} tr_tracker_event;
typedef void (*tr_tracker_callback)(tr_torrent* tor, tr_tracker_event const* event, void* client_data);

View file

@ -118,7 +118,6 @@ struct peer_atom
uint8_t fromBest; /* the "best" value of where the peer has been found */
uint8_t flags; /* these match the added_f flags */
uint8_t flags2; /* flags that aren't defined in added_f */
int8_t seedProbability; /* how likely is this to be a seed... [0..100] or -1 for unknown */
int8_t blocklisted; /* -1 for unknown, true for blocklisted, false for not blocklisted */
tr_port port;
@ -194,6 +193,8 @@ typedef struct tr_swarm
tr_peerMsgs* optimistic; /* the optimistic peer, or NULL if none */
int optimisticUnchokeTimeScaler;
bool poolIsAllSeeds;
bool poolIsAllSeedsDirty; /* true if poolIsAllSeeds needs to be recomputed */
bool isRunning;
bool needsCompletenessCheck;
@ -594,37 +595,16 @@ static bool isAtomBlocklisted(tr_session const* session, struct peer_atom* atom)
****
***/
static void atomSetSeedProbability(struct peer_atom* atom, int seedProbability)
{
TR_ASSERT(atom != NULL);
TR_ASSERT(seedProbability >= -1);
TR_ASSERT(seedProbability <= 100);
atom->seedProbability = seedProbability;
if (seedProbability == 100)
{
atom->flags |= ADDED_F_SEED_FLAG;
}
else if (seedProbability != -1)
{
atom->flags &= ~ADDED_F_SEED_FLAG;
}
}
static inline bool atomIsSeed(struct peer_atom const* atom)
{
return atom->seedProbability == 100;
return (atom->flags & ADDED_F_SEED_FLAG) != 0;
}
static void atomSetSeed(tr_swarm const* s, struct peer_atom* atom)
static void atomSetSeed(tr_swarm* s, struct peer_atom* atom)
{
if (!atomIsSeed(atom))
{
tordbg(s, "marking peer %s as a seed", tr_atomAddrStr(atom));
atomSetSeedProbability(atom, 100);
}
tordbg(s, "marking peer %s as a seed", tr_atomAddrStr(atom));
atom->flags |= ADDED_F_SEED_FLAG;
s->poolIsAllSeedsDirty = true;
}
bool tr_peerMgrPeerIsSeed(tr_torrent const* tor, tr_address const* addr)
@ -1942,12 +1922,11 @@ static int getDefaultShelfLife(uint8_t from)
}
}
static void ensureAtomExists(
static struct peer_atom* ensureAtomExists(
tr_swarm* s,
tr_address const* addr,
tr_port const port,
uint8_t const flags,
int8_t const seedProbability,
uint8_t const from)
{
TR_ASSERT(tr_address_is_valid(addr));
@ -1966,7 +1945,6 @@ static void ensureAtomExists(
a->fromBest = from;
a->shelf_date = tr_time() + getDefaultShelfLife(from) + jitter;
a->blocklisted = -1;
atomSetSeedProbability(a, seedProbability);
tr_ptrArrayInsertSorted(&s->pool, a, compareAtomsByAddress);
tordbg(s, "got a new atom: %s", tr_atomAddrStr(a));
@ -1978,13 +1956,12 @@ static void ensureAtomExists(
a->fromBest = from;
}
if (a->seedProbability == -1)
{
atomSetSeedProbability(a, seedProbability);
}
a->flags |= flags;
}
s->poolIsAllSeedsDirty = true;
return a;
}
static int getMaxPeerCount(tr_torrent const* tor)
@ -2076,12 +2053,7 @@ static bool myHandshakeDoneCB(
}
else /* looking good */
{
struct peer_atom* atom;
ensureAtomExists(s, addr, port, 0, -1, TR_PEER_FROM_INCOMING);
atom = getExistingAtom(s, addr);
TR_ASSERT(atom != NULL);
struct peer_atom* atom = ensureAtomExists(s, addr, port, 0, TR_PEER_FROM_INCOMING);
atom->time = tr_time();
atom->piece_data_time = 0;
@ -2206,21 +2178,43 @@ void tr_peerMgrAddIncoming(tr_peerMgr* manager, tr_address* addr, tr_port port,
managerUnlock(manager);
}
void tr_peerMgrAddPex(tr_torrent* tor, uint8_t from, tr_pex const* pex, int8_t seedProbability)
void tr_peerMgrSetSwarmIsAllSeeds(tr_torrent* tor)
{
if (tr_isPex(pex)) /* safeguard against corrupt data */
{
tr_swarm* s = tor->swarm;
managerLock(s->manager);
tr_torrentLock(tor);
if (!tr_sessionIsAddressBlocked(s->manager->session, &pex->addr) &&
tr_swarm* const swarm = tor->swarm;
int atomCount;
struct peer_atom** atoms = (struct peer_atom**)tr_ptrArrayPeek(&swarm->pool, &atomCount);
for (int i = 0; i < atomCount; ++i)
{
atomSetSeed(swarm, atoms[i]);
}
swarm->poolIsAllSeeds = true;
swarm->poolIsAllSeedsDirty = false;
tr_torrentUnlock(tor);
}
size_t tr_peerMgrAddPex(tr_torrent* tor, uint8_t from, tr_pex const* pex, size_t n_pex)
{
size_t n_used = 0;
tr_swarm* s = tor->swarm;
managerLock(s->manager);
for (tr_pex const* const end = pex + n_pex; pex != end; ++pex)
{
if (tr_isPex(pex) && /* safeguard against corrupt data */
!tr_sessionIsAddressBlocked(s->manager->session, &pex->addr) &&
tr_address_is_valid_for_peers(&pex->addr, pex->port))
{
ensureAtomExists(s, &pex->addr, pex->port, pex->flags, seedProbability, from);
ensureAtomExists(s, &pex->addr, pex->port, pex->flags, from);
++n_used;
}
managerUnlock(s->manager);
}
managerUnlock(s->manager);
return n_used;
}
tr_pex* tr_peerMgrCompactToPex(
@ -4170,22 +4164,9 @@ static uint64_t getPeerCandidateScore(tr_torrent const* tor, struct peer_atom co
i = (atom->flags & ADDED_F_CONNECTABLE) != 0 ? 0 : 1;
score = addValToKey(score, 1, i);
/* prefer peers that we might have a chance of uploading to...
so lower seed probability is better */
if (atom->seedProbability == 100)
{
i = 101;
}
else if (atom->seedProbability == -1)
{
i = 100;
}
else
{
i = atom->seedProbability;
}
score = addValToKey(score, 8, i);
/* prefer peers that we might be able to upload to */
i = (atom->flags & ADDED_F_SEED_FLAG) == 0 ? 0 : 1;
score = addValToKey(score, 1, i);
/* Prefer peers that we got from more trusted sources.
* lower `fromBest' values indicate more trusted sources */
@ -4256,6 +4237,33 @@ static bool checkBestScoresComeFirst(struct peer_candidate const* candidates, in
#endif /* TR_ENABLE_ASSERTS */
static bool calculateAllSeeds(struct tr_swarm* swarm)
{
int nAtoms = 0;
struct peer_atom** atoms = (struct peer_atom**)tr_ptrArrayPeek(&swarm->pool, &nAtoms);
for (int i = 0; i < nAtoms; ++i)
{
if (!atomIsSeed(atoms[i]))
{
return false;
}
}
return true;
}
static bool swarmIsAllSeeds(struct tr_swarm* swarm)
{
if (swarm->poolIsAllSeedsDirty)
{
swarm->poolIsAllSeeds = calculateAllSeeds(swarm);
swarm->poolIsAllSeedsDirty = false;
}
return swarm->poolIsAllSeeds;
}
/** @return an array of all the atoms we might want to connect to */
static struct peer_candidate* getPeerCandidates(tr_session* session, int* candidateCount, int max)
{
@ -4303,6 +4311,14 @@ static struct peer_candidate* getPeerCandidates(tr_session* session, int* candid
continue;
}
/* if everyone in the swarm is seeds and pex is disabled because
* the torrent is private, then don't initiate connections */
bool const seeding = tr_torrentIsSeed(tor);
if (seeding && swarmIsAllSeeds(tor->swarm) && tr_torrentIsPrivate(tor))
{
continue;
}
/* if we've already got enough peers in this torrent... */
if (tr_torrentGetPeerLimit(tor) <= tr_ptrArraySize(&tor->swarm->peers))
{
@ -4310,7 +4326,7 @@ static struct peer_candidate* getPeerCandidates(tr_session* session, int* candid
}
/* if we've already got enough speed in this torrent... */
if (tr_torrentIsSeed(tor) && isBandwidthMaxedOut(&tor->bandwidth, now_msec, TR_UP))
if (seeding && isBandwidthMaxedOut(&tor->bandwidth, now_msec, TR_UP))
{
continue;
}
@ -4393,8 +4409,8 @@ static void initiateCandidateConnection(tr_peerMgr* mgr, struct peer_candidate*
{
#if 0
fprintf(stderr, "Starting an OUTGOING connection with %s - [%s] seedProbability==%d; %s, %s\n", tr_atomAddrStr(c->atom),
tr_torrentName(c->tor), (int)c->atom->seedProbability, tr_torrentIsPrivate(c->tor) ? "private" : "public",
fprintf(stderr, "Starting an OUTGOING connection with %s - [%s] %s, %s\n", tr_atomAddrStr(c->atom),
tr_torrentName(c->tor), tr_torrentIsPrivate(c->tor) ? "private" : "public",
tr_torrentIsSeed(c->tor) ? "seed" : "downloader");
#endif
@ -4405,9 +4421,7 @@ static void initiateCandidateConnection(tr_peerMgr* mgr, struct peer_candidate*
static void makeNewPeerConnections(struct tr_peerMgr* mgr, int const max)
{
int n;
struct peer_candidate* candidates;
candidates = getPeerCandidates(mgr->session, &n, max);
struct peer_candidate* candidates = getPeerCandidates(mgr->session, &n, max);
for (int i = 0; i < n && i < max; ++i)
{

View file

@ -108,10 +108,9 @@ tr_pex* tr_peerMgrCompact6ToPex(
size_t added_f_len,
size_t* pexCount);
/**
* @param seedProbability [0..100] for likelihood that the peer is a seed; -1 for unknown
*/
void tr_peerMgrAddPex(tr_torrent* tor, uint8_t from, tr_pex const* pex, int8_t seedProbability);
size_t tr_peerMgrAddPex(tr_torrent* tor, uint8_t from, tr_pex const* pex, size_t n_pex);
void tr_peerMgrSetSwarmIsAllSeeds(tr_torrent* tor);
enum
{

View file

@ -951,7 +951,6 @@ static void parseLtepHandshake(tr_peerMsgs* msgs, uint32_t len, struct evbuffer*
uint8_t const* addr;
size_t addr_len;
tr_pex pex;
int8_t seedProbability = -1;
memset(&pex, 0, sizeof(tr_pex));
@ -1023,7 +1022,7 @@ static void parseLtepHandshake(tr_peerMsgs* msgs, uint32_t len, struct evbuffer*
/* look for upload_only (BEP 21) */
if (tr_variantDictFindInt(&val, TR_KEY_upload_only, &i))
{
seedProbability = i == 0 ? 0 : 100;
pex.flags |= ADDED_F_SEED_FLAG;
}
/* get peer's listening port */
@ -1038,14 +1037,14 @@ static void parseLtepHandshake(tr_peerMsgs* msgs, uint32_t len, struct evbuffer*
{
pex.addr.type = TR_AF_INET;
memcpy(&pex.addr.addr.addr4, addr, 4);
tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, seedProbability);
tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, 1);
}
if (tr_peerIoIsIncoming(msgs->io) && tr_variantDictFindRaw(&val, TR_KEY_ipv6, &addr, &addr_len) && addr_len == 16)
{
pex.addr.type = TR_AF_INET6;
memcpy(&pex.addr.addr.addr6, addr, 16);
tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, seedProbability);
tr_peerMgrAddPex(msgs->torrent, TR_PEER_FROM_LTEP, &pex, 1);
}
/* get peer's maximum request queue size */
@ -1166,20 +1165,8 @@ static void parseUtPex(tr_peerMsgs* msgs, uint32_t msglen, struct evbuffer* inbu
}
pex = tr_peerMgrCompactToPex(added, added_len, added_f, added_f_len, &n);
n = MIN(n, MAX_PEX_PEER_COUNT);
for (size_t i = 0; i < n; ++i)
{
int seedProbability = -1;
if (i < added_f_len)
{
seedProbability = (added_f[i] & ADDED_F_SEED_FLAG) != 0 ? 100 : 0;
}
tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, pex + i, seedProbability);
}
tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, pex, n);
tr_free(pex);
}
@ -1198,20 +1185,8 @@ static void parseUtPex(tr_peerMsgs* msgs, uint32_t msglen, struct evbuffer* inbu
}
pex = tr_peerMgrCompact6ToPex(added, added_len, added_f, added_f_len, &n);
n = MIN(n, MAX_PEX_PEER_COUNT);
for (size_t i = 0; i < n; ++i)
{
int seedProbability = -1;
if (i < added_f_len)
{
seedProbability = (added_f[i] & ADDED_F_SEED_FLAG) != 0 ? 100 : 0;
}
tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, pex + i, seedProbability);
}
tr_peerMgrAddPex(tor, TR_PEER_FROM_PEX, pex, n);
tr_free(pex);
}

View file

@ -64,24 +64,14 @@ static void savePeers(tr_variant* dict, tr_torrent const* tor)
tr_free(pex);
}
static int addPeers(tr_torrent* tor, uint8_t const* buf, size_t buflen)
static size_t addPeers(tr_torrent* tor, uint8_t const* buf, size_t buflen)
{
int numAdded = 0;
size_t const count = buflen / sizeof(tr_pex);
size_t const n_in = buflen / sizeof(tr_pex);
size_t const n_pex = MIN(n_in, MAX_REMEMBERED_PEERS);
for (size_t i = 0; i < count && numAdded < MAX_REMEMBERED_PEERS; ++i)
{
tr_pex pex;
memcpy(&pex, buf + i * sizeof(tr_pex), sizeof(tr_pex));
if (tr_isPex(&pex))
{
tr_peerMgrAddPex(tor, TR_PEER_FROM_RESUME, &pex, -1);
++numAdded;
}
}
return numAdded;
tr_pex pex[MAX_REMEMBERED_PEERS];
memcpy(pex, buf, sizeof(tr_pex) * n_pex);
return tr_peerMgrAddPex(tor, TR_PEER_FROM_RESUME, pex, n_pex);
}
static uint64_t loadPeers(tr_variant* dict, tr_torrent* tor)
@ -92,15 +82,15 @@ static uint64_t loadPeers(tr_variant* dict, tr_torrent* tor)
if (tr_variantDictFindRaw(dict, TR_KEY_peers2, &str, &len))
{
int const numAdded = addPeers(tor, str, len);
tr_logAddTorDbg(tor, "Loaded %d IPv4 peers from resume file", numAdded);
size_t const numAdded = addPeers(tor, str, len);
tr_logAddTorDbg(tor, "Loaded %zu IPv4 peers from resume file", numAdded);
ret = TR_FR_PEERS;
}
if (tr_variantDictFindRaw(dict, TR_KEY_peers2_6, &str, &len))
{
int const numAdded = addPeers(tor, str, len);
tr_logAddTorDbg(tor, "Loaded %d IPv6 peers from resume file", numAdded);
size_t const numAdded = addPeers(tor, str, len);
tr_logAddTorDbg(tor, "Loaded %zu IPv6 peers from resume file", numAdded);
ret = TR_FR_PEERS;
}

View file

@ -559,27 +559,18 @@ static void onTrackerResponse(tr_torrent* tor, tr_tracker_event const* event, vo
switch (event->messageType)
{
case TR_TRACKER_PEERS:
tr_logAddTorDbg(tor, "Got %zu peers from tracker", event->pexCount);
tr_peerMgrAddPex(tor, TR_PEER_FROM_TRACKER, event->pex, event->pexCount);
break;
case TR_TRACKER_COUNTS:
if (tr_torrentIsPrivate(tor) && (event->leechers == 0))
{
int8_t const seedProbability = event->seedProbability;
bool const allAreSeeds = seedProbability == 100;
if (allAreSeeds)
{
tr_logAddTorDbg(tor, "Got %zu seeds from tracker", event->pexCount);
}
else
{
tr_logAddTorDbg(tor, "Got %zu peers from tracker", event->pexCount);
}
for (size_t i = 0; i < event->pexCount; ++i)
{
tr_peerMgrAddPex(tor, TR_PEER_FROM_TRACKER, &event->pex[i], seedProbability);
}
break;
tr_peerMgrSetSwarmIsAllSeeds(tor);
}
break;
case TR_TRACKER_WARNING:
tr_logAddTorErr(tor, _("Tracker warning: \"%s\""), event->text);
tor->error = TR_STAT_TRACKER_WARNING;

View file

@ -674,10 +674,7 @@ static void callback(void* ignore, int event, unsigned char const* info_hash, vo
pex = tr_peerMgrCompact6ToPex(data, data_len, NULL, 0, &n);
}
for (size_t i = 0; i < n; ++i)
{
tr_peerMgrAddPex(tor, TR_PEER_FROM_DHT, pex + i, -1);
}
tr_peerMgrAddPex(tor, TR_PEER_FROM_DHT, pex, n);
tr_free(pex);
tr_logAddTorDbg(tor, "Learned %d %s peers from DHT", (int)n, event == DHT_EVENT_VALUES6 ? "IPv6" : "IPv4");

View file

@ -592,7 +592,7 @@ static int tr_lpdConsiderAnnounce(tr_pex* peer, char const* const msg)
if (tr_isTorrent(tor) && tr_torrentAllowsLPD(tor))
{
/* we found a suitable peer, add it to the torrent */
tr_peerMgrAddPex(tor, TR_PEER_FROM_LPD, peer, -1);
tr_peerMgrAddPex(tor, TR_PEER_FROM_LPD, peer, 1);
tr_logAddTorDbg(tor, "Learned %d local peer from LPD (%s:%u)", 1, tr_address_to_string(&peer->addr), peerPort);
/* periodic reconnectPulse() deals with the rest... */

View file

@ -4377,8 +4377,7 @@ static void removeKeRangerRansomware()
{
[segmentedControl setImage:[NSImage imageNamed:@"ToolbarPauseAllTemplate"] forSegment:TOOLBAR_PAUSE_TAG];
}
[segmentedCell setToolTip:NSLocalizedString(@"Pause all transfers", "All toolbar item -> tooltip")
forSegment:TOOLBAR_PAUSE_TAG];
[segmentedCell setToolTip:NSLocalizedString(@"Pause all transfers", "All toolbar item -> tooltip") forSegment:TOOLBAR_PAUSE_TAG];
[segmentedCell setTag:TOOLBAR_RESUME_TAG forSegment:TOOLBAR_RESUME_TAG];
[segmentedControl setImage:[NSImage imageNamed:@"ToolbarResumeAllTemplate"] forSegment:TOOLBAR_RESUME_TAG];

View file

@ -1993,11 +1993,7 @@ bool trashDataFile(char const* filename, tr_error** error)
tempNode = [[FileListNode alloc] initWithFolderName:pathComponents[0] path:@"" torrent:self];
}
[self insertPathForComponents:pathComponents
withComponentIndex:1
forParent:tempNode
fileSize:file->length
index:i
[self insertPathForComponents:pathComponents withComponentIndex:1 forParent:tempNode fileSize:file->length index:i
flatList:flatFileList];
}