1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 17:47:37 +00:00
transmission/libtransmission/torrent.c
Jordan Lee fdec244f04 (trunk libT) #4336 "availablility nonsense" -- fix bug in tr_cpMissingBytesInPiece() introduced last week by r12515 for #4332. Add assertions to the nightly build to watch for regressions of this fix.
The bug was that I fixed #4332's off-by-one improperly in tr_cpMissingBlocksInPiece(). The piece's last block has to be calculated separately because its byte size may be different than the other blocks, The mistake in r12515 was that the last block could wind up being counted twice.
2011-07-02 13:20:17 +00:00

3077 lines
81 KiB
C

/*
* This file Copyright (C) Mnemosyne LLC
*
* This file is licensed by the GPL version 2. Works owned by the
* Transmission project are granted a special exemption to clause 2(b)
* so that the bulk of its code can remain under the MIT license.
* This exemption does not extend to derived works not owned by
* the Transmission project.
*
* $Id$
*/
#include <signal.h> /* signal() */
#include <sys/types.h> /* stat */
#include <sys/stat.h> /* stat */
#ifndef WIN32
#include <sys/wait.h> /* wait() */
#else
#include <process.h>
#define waitpid(pid, status, options) _cwait(status, pid, WAIT_CHILD)
#endif
#include <unistd.h> /* stat */
#include <dirent.h>
#include <assert.h>
#include <math.h>
#include <stdarg.h>
#include <string.h> /* memcmp */
#include <stdlib.h> /* qsort */
#include <stdio.h> /* remove() */
#include <event2/util.h> /* evutil_vsnprintf() */
#include "transmission.h"
#include "announcer.h"
#include "bandwidth.h"
#include "bencode.h"
#include "cache.h"
#include "completion.h"
#include "crypto.h" /* for tr_sha1 */
#include "resume.h"
#include "fdlimit.h" /* tr_fdTorrentClose */
#include "inout.h" /* tr_ioTestPiece() */
#include "magnet.h"
#include "metainfo.h"
#include "peer-common.h" /* MAX_BLOCK_SIZE */
#include "peer-mgr.h"
#include "platform.h" /* TR_PATH_DELIMITER_STR */
#include "ptrarray.h"
#include "session.h"
#include "torrent.h"
#include "torrent-magnet.h"
#include "trevent.h" /* tr_runInEventThread() */
#include "utils.h"
#include "verify.h"
#include "version.h"
/***
****
***/
#define tr_deeplog_tor( tor, ... ) \
do { \
if( tr_deepLoggingIsActive( ) ) \
tr_deepLog( __FILE__, __LINE__, tr_torrentName( tor ), __VA_ARGS__ ); \
} while( 0 )
/***
****
***/
const char *
tr_torrentName( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->info.name;
}
int
tr_torrentId( const tr_torrent * tor )
{
return tor->uniqueId;
}
tr_torrent*
tr_torrentFindFromId( tr_session * session, int id )
{
tr_torrent * tor = NULL;
while(( tor = tr_torrentNext( session, tor )))
if( tor->uniqueId == id )
return tor;
return NULL;
}
tr_torrent*
tr_torrentFindFromHashString( tr_session * session, const char * str )
{
tr_torrent * tor = NULL;
while(( tor = tr_torrentNext( session, tor )))
if( !evutil_ascii_strcasecmp( str, tor->info.hashString ) )
return tor;
return NULL;
}
tr_torrent*
tr_torrentFindFromHash( tr_session * session, const uint8_t * torrentHash )
{
tr_torrent * tor = NULL;
while(( tor = tr_torrentNext( session, tor )))
if( *tor->info.hash == *torrentHash )
if( !memcmp( tor->info.hash, torrentHash, SHA_DIGEST_LENGTH ) )
return tor;
return NULL;
}
tr_torrent*
tr_torrentFindFromMagnetLink( tr_session * session, const char * magnet )
{
tr_magnet_info * info;
tr_torrent * tor = NULL;
if(( info = tr_magnetParse( magnet )))
{
tor = tr_torrentFindFromHash( session, info->hash );
tr_magnetFree( info );
}
return tor;
}
tr_torrent*
tr_torrentFindFromObfuscatedHash( tr_session * session,
const uint8_t * obfuscatedTorrentHash )
{
tr_torrent * tor = NULL;
while(( tor = tr_torrentNext( session, tor )))
if( !memcmp( tor->obfuscatedHash, obfuscatedTorrentHash,
SHA_DIGEST_LENGTH ) )
return tor;
return NULL;
}
bool
tr_torrentIsPieceTransferAllowed( const tr_torrent * tor,
tr_direction direction )
{
int limit;
bool allowed = true;
if( tr_torrentUsesSpeedLimit( tor, direction ) )
if( tr_torrentGetSpeedLimit_Bps( tor, direction ) <= 0 )
allowed = false;
if( tr_torrentUsesSessionLimits( tor ) )
if( tr_sessionGetActiveSpeedLimit_Bps( tor->session, direction, &limit ) )
if( limit <= 0 )
allowed = false;
return allowed;
}
/***
**** PER-TORRENT UL / DL SPEEDS
***/
void
tr_torrentSetSpeedLimit_Bps( tr_torrent * tor, tr_direction dir, int Bps )
{
assert( tr_isTorrent( tor ) );
assert( tr_isDirection( dir ) );
assert( Bps >= 0 );
if( tr_bandwidthSetDesiredSpeed_Bps( &tor->bandwidth, dir, Bps ) )
tr_torrentSetDirty( tor );
}
void
tr_torrentSetSpeedLimit_KBps( tr_torrent * tor, tr_direction dir, int KBps )
{
tr_torrentSetSpeedLimit_Bps( tor, dir, toSpeedBytes( KBps ) );
}
int
tr_torrentGetSpeedLimit_Bps( const tr_torrent * tor, tr_direction dir )
{
assert( tr_isTorrent( tor ) );
assert( tr_isDirection( dir ) );
return tr_bandwidthGetDesiredSpeed_Bps( &tor->bandwidth, dir );
}
int
tr_torrentGetSpeedLimit_KBps( const tr_torrent * tor, tr_direction dir )
{
return toSpeedKBps( tr_torrentGetSpeedLimit_Bps( tor, dir ) );
}
void
tr_torrentUseSpeedLimit( tr_torrent * tor, tr_direction dir, bool do_use )
{
assert( tr_isTorrent( tor ) );
assert( tr_isDirection( dir ) );
if( tr_bandwidthSetLimited( &tor->bandwidth, dir, do_use ) )
tr_torrentSetDirty( tor );
}
bool
tr_torrentUsesSpeedLimit( const tr_torrent * tor, tr_direction dir )
{
assert( tr_isTorrent( tor ) );
assert( tr_isDirection( dir ) );
return tr_bandwidthIsLimited( &tor->bandwidth, dir );
}
void
tr_torrentUseSessionLimits( tr_torrent * tor, bool doUse )
{
bool changed;
assert( tr_isTorrent( tor ) );
changed = tr_bandwidthHonorParentLimits( &tor->bandwidth, TR_UP, doUse );
changed |= tr_bandwidthHonorParentLimits( &tor->bandwidth, TR_DOWN, doUse );
if( changed )
tr_torrentSetDirty( tor );
}
bool
tr_torrentUsesSessionLimits( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tr_bandwidthAreParentLimitsHonored( &tor->bandwidth, TR_UP );
}
/***
****
***/
void
tr_torrentSetRatioMode( tr_torrent * tor, tr_ratiolimit mode )
{
assert( tr_isTorrent( tor ) );
assert( mode==TR_RATIOLIMIT_GLOBAL || mode==TR_RATIOLIMIT_SINGLE || mode==TR_RATIOLIMIT_UNLIMITED );
if( mode != tor->ratioLimitMode )
{
tor->ratioLimitMode = mode;
tr_torrentSetDirty( tor );
}
}
tr_ratiolimit
tr_torrentGetRatioMode( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->ratioLimitMode;
}
void
tr_torrentSetRatioLimit( tr_torrent * tor, double desiredRatio )
{
assert( tr_isTorrent( tor ) );
if( (int)(desiredRatio*100.0) != (int)(tor->desiredRatio*100.0) )
{
tor->desiredRatio = desiredRatio;
tr_torrentSetDirty( tor );
}
}
double
tr_torrentGetRatioLimit( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->desiredRatio;
}
bool
tr_torrentGetSeedRatio( const tr_torrent * tor, double * ratio )
{
bool isLimited;
switch( tr_torrentGetRatioMode( tor ) )
{
case TR_RATIOLIMIT_SINGLE:
isLimited = true;
if( ratio )
*ratio = tr_torrentGetRatioLimit( tor );
break;
case TR_RATIOLIMIT_GLOBAL:
isLimited = tr_sessionIsRatioLimited( tor->session );
if( isLimited && ratio )
*ratio = tr_sessionGetRatioLimit( tor->session );
break;
default: /* TR_RATIOLIMIT_UNLIMITED */
isLimited = false;
break;
}
return isLimited;
}
/* returns true if the seed ratio applies --
* it applies if the torrent's a seed AND it has a seed ratio set */
static bool
tr_torrentGetSeedRatioBytes( tr_torrent * tor,
uint64_t * setmeLeft,
uint64_t * setmeGoal )
{
double seedRatio;
bool seedRatioApplies = false;
if( tr_torrentGetSeedRatio( tor, &seedRatio ) )
{
const uint64_t u = tor->uploadedCur + tor->uploadedPrev;
const uint64_t d = tor->downloadedCur + tor->downloadedPrev;
const uint64_t baseline = d ? d : tr_cpSizeWhenDone( &tor->completion );
const uint64_t goal = baseline * seedRatio;
if( setmeLeft ) *setmeLeft = goal > u ? goal - u : 0;
if( setmeGoal ) *setmeGoal = goal;
seedRatioApplies = tr_torrentIsSeed( tor );
}
return seedRatioApplies;
}
static bool
tr_torrentIsSeedRatioDone( tr_torrent * tor )
{
uint64_t bytesLeft;
return tr_torrentGetSeedRatioBytes( tor, &bytesLeft, NULL ) && !bytesLeft;
}
/***
****
***/
void
tr_torrentSetIdleMode( tr_torrent * tor, tr_idlelimit mode )
{
assert( tr_isTorrent( tor ) );
assert( mode==TR_IDLELIMIT_GLOBAL || mode==TR_IDLELIMIT_SINGLE || mode==TR_IDLELIMIT_UNLIMITED );
if( mode != tor->idleLimitMode )
{
tor->idleLimitMode = mode;
tr_torrentSetDirty( tor );
}
}
tr_idlelimit
tr_torrentGetIdleMode( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->idleLimitMode;
}
void
tr_torrentSetIdleLimit( tr_torrent * tor, uint16_t idleMinutes )
{
assert( tr_isTorrent( tor ) );
if( idleMinutes > 0 )
{
tor->idleLimitMinutes = idleMinutes;
tr_torrentSetDirty( tor );
}
}
uint16_t
tr_torrentGetIdleLimit( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->idleLimitMinutes;
}
bool
tr_torrentGetSeedIdle( const tr_torrent * tor, uint16_t * idleMinutes )
{
bool isLimited;
switch( tr_torrentGetIdleMode( tor ) )
{
case TR_IDLELIMIT_SINGLE:
isLimited = true;
if( idleMinutes )
*idleMinutes = tr_torrentGetIdleLimit( tor );
break;
case TR_IDLELIMIT_GLOBAL:
isLimited = tr_sessionIsIdleLimited( tor->session );
if( isLimited && idleMinutes )
*idleMinutes = tr_sessionGetIdleLimit( tor->session );
break;
default: /* TR_IDLELIMIT_UNLIMITED */
isLimited = false;
break;
}
return isLimited;
}
static bool
tr_torrentIsSeedIdleLimitDone( tr_torrent * tor )
{
uint16_t idleMinutes;
return tr_torrentGetSeedIdle( tor, &idleMinutes )
&& difftime(tr_time(), MAX(tor->startDate, tor->activityDate)) >= idleMinutes * 60u;
}
/***
****
***/
void
tr_torrentCheckSeedLimit( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
if( !tor->isRunning || !tr_torrentIsSeed( tor ) )
return;
/* if we're seeding and reach our seed ratio limit, stop the torrent */
if( tr_torrentIsSeedRatioDone( tor ) )
{
tr_torinf( tor, "Seed ratio reached; pausing torrent" );
tor->isStopping = true;
/* maybe notify the client */
if( tor->ratio_limit_hit_func != NULL )
tor->ratio_limit_hit_func( tor, tor->ratio_limit_hit_func_user_data );
}
/* if we're seeding and reach our inactiviy limit, stop the torrent */
else if( tr_torrentIsSeedIdleLimitDone( tor ) )
{
tr_torinf( tor, "Seeding idle limit reached; pausing torrent" );
tor->isStopping = true;
tor->finishedSeedingByIdle = true;
/* maybe notify the client */
if( tor->idle_limit_hit_func != NULL )
tor->idle_limit_hit_func( tor, tor->idle_limit_hit_func_user_data );
}
}
/***
****
***/
void
tr_torrentSetLocalError( tr_torrent * tor, const char * fmt, ... )
{
va_list ap;
assert( tr_isTorrent( tor ) );
va_start( ap, fmt );
tor->error = TR_STAT_LOCAL_ERROR;
tor->errorTracker[0] = '\0';
evutil_vsnprintf( tor->errorString, sizeof( tor->errorString ), fmt, ap );
va_end( ap );
tr_torerr( tor, "%s", tor->errorString );
if( tor->isRunning )
tor->isStopping = true;
}
static void
tr_torrentClearError( tr_torrent * tor )
{
tor->error = TR_STAT_OK;
tor->errorString[0] = '\0';
tor->errorTracker[0] = '\0';
}
static void
onTrackerResponse( tr_torrent * tor, const tr_tracker_event * event, void * unused UNUSED )
{
switch( event->messageType )
{
case TR_TRACKER_PEERS:
{
size_t i;
const int8_t seedProbability = event->seedProbability;
const bool allAreSeeds = seedProbability == 100;
if( allAreSeeds )
tr_tordbg( tor, "Got %zu seeds from tracker", event->pexCount );
else
tr_tordbg( tor, "Got %zu peers from tracker", event->pexCount );
for( i = 0; i < event->pexCount; ++i )
tr_peerMgrAddPex( tor, TR_PEER_FROM_TRACKER, &event->pex[i], seedProbability );
if( allAreSeeds && tr_torrentIsPrivate( tor ) )
tr_peerMgrMarkAllAsSeeds( tor );
break;
}
case TR_TRACKER_WARNING:
tr_torerr( tor, _( "Tracker warning: \"%s\"" ), event->text );
tor->error = TR_STAT_TRACKER_WARNING;
tr_strlcpy( tor->errorTracker, event->tracker, sizeof( tor->errorTracker ) );
tr_strlcpy( tor->errorString, event->text, sizeof( tor->errorString ) );
break;
case TR_TRACKER_ERROR:
tr_torerr( tor, _( "Tracker error: \"%s\"" ), event->text );
tor->error = TR_STAT_TRACKER_ERROR;
tr_strlcpy( tor->errorTracker, event->tracker, sizeof( tor->errorTracker ) );
tr_strlcpy( tor->errorString, event->text, sizeof( tor->errorString ) );
break;
case TR_TRACKER_ERROR_CLEAR:
if( tor->error != TR_STAT_LOCAL_ERROR )
tr_torrentClearError( tor );
break;
}
}
/***
****
**** TORRENT INSTANTIATION
****
***/
static tr_piece_index_t
getBytePiece( const tr_info * info, uint64_t byteOffset )
{
assert( info );
assert( info->pieceSize != 0 );
return byteOffset / info->pieceSize;
}
static void
initFilePieces( tr_info * info,
tr_file_index_t fileIndex )
{
tr_file * file;
uint64_t firstByte, lastByte;
assert( info );
assert( fileIndex < info->fileCount );
file = &info->files[fileIndex];
firstByte = file->offset;
lastByte = firstByte + ( file->length ? file->length - 1 : 0 );
file->firstPiece = getBytePiece( info, firstByte );
file->lastPiece = getBytePiece( info, lastByte );
}
static int
pieceHasFile( tr_piece_index_t piece,
const tr_file * file )
{
return ( file->firstPiece <= piece ) && ( piece <= file->lastPiece );
}
static tr_priority_t
calculatePiecePriority( const tr_torrent * tor,
tr_piece_index_t piece,
int fileHint )
{
tr_file_index_t i;
tr_priority_t priority = TR_PRI_LOW;
/* find the first file that has data in this piece */
if( fileHint >= 0 ) {
i = fileHint;
while( i > 0 && pieceHasFile( piece, &tor->info.files[i - 1] ) )
--i;
} else {
for( i = 0; i < tor->info.fileCount; ++i )
if( pieceHasFile( piece, &tor->info.files[i] ) )
break;
}
/* the piece's priority is the max of the priorities
* of all the files in that piece */
for( ; i < tor->info.fileCount; ++i )
{
const tr_file * file = &tor->info.files[i];
if( !pieceHasFile( piece, file ) )
break;
priority = MAX( priority, file->priority );
/* when dealing with multimedia files, getting the first and
last pieces can sometimes allow you to preview it a bit
before it's fully downloaded... */
if( file->priority >= TR_PRI_NORMAL )
if( file->firstPiece == piece || file->lastPiece == piece )
priority = TR_PRI_HIGH;
}
return priority;
}
static void
tr_torrentInitFilePieces( tr_torrent * tor )
{
int * firstFiles;
tr_file_index_t f;
tr_piece_index_t p;
uint64_t offset = 0;
tr_info * inf = &tor->info;
/* assign the file offsets */
for( f=0; f<inf->fileCount; ++f ) {
inf->files[f].offset = offset;
offset += inf->files[f].length;
initFilePieces( inf, f );
}
/* build the array of first-file hints to give calculatePiecePriority */
firstFiles = tr_new( int, inf->pieceCount );
for( p=f=0; p<inf->pieceCount; ++p ) {
while( inf->files[f].lastPiece < p )
++f;
firstFiles[p] = f;
}
#if 0
/* test to confirm the first-file hints are correct */
for( p=0; p<inf->pieceCount; ++p ) {
f = firstFiles[p];
assert( inf->files[f].firstPiece <= p );
assert( inf->files[f].lastPiece >= p );
if( f > 0 )
assert( inf->files[f-1].lastPiece < p );
for( f=0; f<inf->fileCount; ++f )
if( pieceHasFile( p, &inf->files[f] ) )
break;
assert( (int)f == firstFiles[p] );
}
#endif
for( p=0; p<inf->pieceCount; ++p )
inf->pieces[p].priority = calculatePiecePriority( tor, p, firstFiles[p] );
tr_free( firstFiles );
}
static void torrentStart( tr_torrent * tor );
/**
* Decide on a block size. Constraints:
* (1) most clients decline requests over 16 KiB
* (2) pieceSize must be a multiple of block size
*/
uint32_t
tr_getBlockSize( uint32_t pieceSize )
{
uint32_t b = pieceSize;
while( b > MAX_BLOCK_SIZE )
b /= 2u;
if( !b || ( pieceSize % b ) ) /* not cleanly divisible */
return 0;
return b;
}
static void refreshCurrentDir( tr_torrent * tor );
static void
torrentInitFromInfo( tr_torrent * tor )
{
uint64_t t;
tr_info * info = &tor->info;
tor->blockSize = tr_getBlockSize( info->pieceSize );
if( info->pieceSize )
tor->lastPieceSize = (uint32_t)(info->totalSize % info->pieceSize);
if( !tor->lastPieceSize )
tor->lastPieceSize = info->pieceSize;
if( tor->blockSize )
tor->lastBlockSize = info->totalSize % tor->blockSize;
if( !tor->lastBlockSize )
tor->lastBlockSize = tor->blockSize;
tor->blockCount = tor->blockSize
? ( info->totalSize + tor->blockSize - 1 ) / tor->blockSize
: 0;
tor->blockCountInPiece = tor->blockSize
? info->pieceSize / tor->blockSize
: 0;
tor->blockCountInLastPiece = tor->blockSize
? ( tor->lastPieceSize + tor->blockSize - 1 ) / tor->blockSize
: 0;
/* check our work */
if( tor->blockSize != 0 )
assert( ( info->pieceSize % tor->blockSize ) == 0 );
t = info->pieceCount - 1;
t *= info->pieceSize;
t += tor->lastPieceSize;
assert( t == info->totalSize );
t = tor->blockCount - 1;
t *= tor->blockSize;
t += tor->lastBlockSize;
assert( t == info->totalSize );
t = info->pieceCount - 1;
t *= tor->blockCountInPiece;
t += tor->blockCountInLastPiece;
assert( t == (uint64_t)tor->blockCount );
tr_cpConstruct( &tor->completion, tor );
tr_torrentInitFilePieces( tor );
tor->completeness = tr_cpGetStatus( &tor->completion );
}
static void tr_torrentFireMetadataCompleted( tr_torrent * tor );
void
tr_torrentGotNewInfoDict( tr_torrent * tor )
{
torrentInitFromInfo( tor );
tr_peerMgrOnTorrentGotMetainfo( tor );
tr_torrentFireMetadataCompleted( tor );
}
static bool
hasAnyLocalData( const tr_torrent * tor )
{
tr_file_index_t i;
for( i=0; i<tor->info.fileCount; ++i )
if( tr_torrentFindFile2( tor, i, NULL, NULL, NULL ) )
return true;
return false;
}
static bool
setLocalErrorIfFilesDisappeared( tr_torrent * tor )
{
const bool disappeared = ( tr_cpHaveTotal( &tor->completion ) > 0 ) && !hasAnyLocalData( tor );
if( disappeared )
{
tr_deeplog_tor( tor, "%s", "[LAZY] uh oh, the files disappeared" );
tr_torrentSetLocalError( tor, "%s", _( "No data found! Ensure your drives are connected or use \"Set Location\". To re-download, remove the torrent and re-add it." ) );
}
return disappeared;
}
static void
torrentInit( tr_torrent * tor, const tr_ctor * ctor )
{
int doStart;
uint64_t loaded;
const char * dir;
bool isNewTorrent;
struct stat st;
static int nextUniqueId = 1;
tr_session * session = tr_ctorGetSession( ctor );
assert( session != NULL );
tr_sessionLock( session );
tor->session = session;
tor->uniqueId = nextUniqueId++;
tor->magicNumber = TORRENT_MAGIC_NUMBER;
tr_peerIdInit( tor->peer_id );
tr_sha1( tor->obfuscatedHash, "req2", 4,
tor->info.hash, SHA_DIGEST_LENGTH,
NULL );
if( !tr_ctorGetDownloadDir( ctor, TR_FORCE, &dir ) ||
!tr_ctorGetDownloadDir( ctor, TR_FALLBACK, &dir ) )
tor->downloadDir = tr_strdup( dir );
if( tr_ctorGetIncompleteDir( ctor, &dir ) )
dir = tr_sessionGetIncompleteDir( session );
if( tr_sessionIsIncompleteDirEnabled( session ) )
tor->incompleteDir = tr_strdup( dir );
tr_bandwidthConstruct( &tor->bandwidth, session, &session->bandwidth );
tor->bandwidth.priority = tr_ctorGetBandwidthPriority( ctor );
tor->error = TR_STAT_OK;
tor->finishedSeedingByIdle = false;
tr_peerMgrAddTorrent( session->peerMgr, tor );
assert( !tor->downloadedCur );
assert( !tor->uploadedCur );
tr_torrentSetAddedDate( tor, tr_time( ) ); /* this is a default value to be
overwritten by the resume file */
torrentInitFromInfo( tor );
loaded = tr_torrentLoadResume( tor, ~0, ctor );
tor->completeness = tr_cpGetStatus( &tor->completion );
setLocalErrorIfFilesDisappeared( tor );
tr_ctorInitTorrentPriorities( ctor, tor );
tr_ctorInitTorrentWanted( ctor, tor );
refreshCurrentDir( tor );
doStart = tor->isRunning;
tor->isRunning = 0;
if( !( loaded & TR_FR_SPEEDLIMIT ) )
{
tr_torrentUseSpeedLimit( tor, TR_UP, false );
tr_torrentSetSpeedLimit_Bps( tor, TR_UP, tr_sessionGetSpeedLimit_Bps( tor->session, TR_UP ) );
tr_torrentUseSpeedLimit( tor, TR_DOWN, false );
tr_torrentSetSpeedLimit_Bps( tor, TR_DOWN, tr_sessionGetSpeedLimit_Bps( tor->session, TR_DOWN ) );
tr_torrentUseSessionLimits( tor, true );
}
if( !( loaded & TR_FR_RATIOLIMIT ) )
{
tr_torrentSetRatioMode( tor, TR_RATIOLIMIT_GLOBAL );
tr_torrentSetRatioLimit( tor, tr_sessionGetRatioLimit( tor->session ) );
}
if( !( loaded & TR_FR_IDLELIMIT ) )
{
tr_torrentSetIdleMode( tor, TR_IDLELIMIT_GLOBAL );
tr_torrentSetIdleLimit( tor, tr_sessionGetIdleLimit( tor->session ) );
}
/* add the torrent to tr_session.torrentList */
++session->torrentCount;
if( session->torrentList == NULL )
session->torrentList = tor;
else {
tr_torrent * it = session->torrentList;
while( it->next != NULL )
it = it->next;
it->next = tor;
}
/* if we don't have a local .torrent file already, assume the torrent is new */
isNewTorrent = stat( tor->info.torrent, &st );
/* maybe save our own copy of the metainfo */
if( tr_ctorGetSave( ctor ) )
{
const tr_benc * val;
if( !tr_ctorGetMetainfo( ctor, &val ) )
{
const char * path = tor->info.torrent;
const int err = tr_bencToFile( val, TR_FMT_BENC, path );
if( err )
tr_torrentSetLocalError( tor, "Unable to save torrent file: %s", tr_strerror( err ) );
tr_sessionSetTorrentFile( tor->session, tor->info.hashString, path );
}
}
tor->tiers = tr_announcerAddTorrent( tor, onTrackerResponse, NULL );
if( isNewTorrent )
{
tor->startAfterVerify = doStart;
tr_torrentVerify( tor );
}
else if( doStart )
{
torrentStart( tor );
}
tr_sessionUnlock( session );
}
static tr_parse_result
torrentParseImpl( const tr_ctor * ctor, tr_info * setmeInfo,
bool * setmeHasInfo, int * dictLength )
{
int doFree;
bool didParse;
bool hasInfo = false;
tr_info tmp;
const tr_benc * metainfo;
tr_session * session = tr_ctorGetSession( ctor );
tr_parse_result result = TR_PARSE_OK;
if( setmeInfo == NULL )
setmeInfo = &tmp;
memset( setmeInfo, 0, sizeof( tr_info ) );
if( tr_ctorGetMetainfo( ctor, &metainfo ) )
return TR_PARSE_ERR;
didParse = tr_metainfoParse( session, metainfo, setmeInfo,
&hasInfo, dictLength );
doFree = didParse && ( setmeInfo == &tmp );
if( !didParse )
result = TR_PARSE_ERR;
if( didParse && hasInfo && !tr_getBlockSize( setmeInfo->pieceSize ) )
result = TR_PARSE_ERR;
if( didParse && session && tr_torrentExists( session, setmeInfo->hash ) )
result = TR_PARSE_DUPLICATE;
if( doFree )
tr_metainfoFree( setmeInfo );
if( setmeHasInfo != NULL )
*setmeHasInfo = hasInfo;
return result;
}
tr_parse_result
tr_torrentParse( const tr_ctor * ctor, tr_info * setmeInfo )
{
return torrentParseImpl( ctor, setmeInfo, NULL, NULL );
}
tr_torrent *
tr_torrentNew( const tr_ctor * ctor, int * setmeError )
{
int len;
bool hasInfo;
tr_info tmpInfo;
tr_parse_result r;
tr_torrent * tor = NULL;
assert( ctor != NULL );
assert( tr_isSession( tr_ctorGetSession( ctor ) ) );
r = torrentParseImpl( ctor, &tmpInfo, &hasInfo, &len );
if( r == TR_PARSE_OK )
{
tor = tr_new0( tr_torrent, 1 );
tor->info = tmpInfo;
if( hasInfo )
tor->infoDictLength = len;
torrentInit( tor, ctor );
}
else
{
if( r == TR_PARSE_DUPLICATE )
tr_metainfoFree( &tmpInfo );
if( setmeError )
*setmeError = r;
}
return tor;
}
/**
***
**/
void
tr_torrentSetDownloadDir( tr_torrent * tor, const char * path )
{
assert( tr_isTorrent( tor ) );
if( !path || !tor->downloadDir || strcmp( path, tor->downloadDir ) )
{
tr_free( tor->downloadDir );
tor->downloadDir = tr_strdup( path );
tr_torrentSetDirty( tor );
}
refreshCurrentDir( tor );
}
const char*
tr_torrentGetDownloadDir( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->downloadDir;
}
const char *
tr_torrentGetCurrentDir( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->currentDir;
}
void
tr_torrentChangeMyPort( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
if( tor->isRunning )
tr_announcerChangeMyPort( tor );
}
static inline void
tr_torrentManualUpdateImpl( void * vtor )
{
tr_torrent * tor = vtor;
assert( tr_isTorrent( tor ) );
if( tor->isRunning )
tr_announcerManualAnnounce( tor );
}
void
tr_torrentManualUpdate( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
tr_runInEventThread( tor->session, tr_torrentManualUpdateImpl, tor );
}
bool
tr_torrentCanManualUpdate( const tr_torrent * tor )
{
return ( tr_isTorrent( tor ) )
&& ( tor->isRunning )
&& ( tr_announcerCanManualAnnounce( tor ) );
}
const tr_info *
tr_torrentInfo( const tr_torrent * tor )
{
return tr_isTorrent( tor ) ? &tor->info : NULL;
}
const tr_stat *
tr_torrentStatCached( tr_torrent * tor )
{
const time_t now = tr_time( );
return tr_isTorrent( tor ) && ( now == tor->lastStatTime )
? &tor->stats
: tr_torrentStat( tor );
}
void
tr_torrentSetVerifyState( tr_torrent * tor, tr_verify_state state )
{
assert( tr_isTorrent( tor ) );
assert( state==TR_VERIFY_NONE || state==TR_VERIFY_WAIT || state==TR_VERIFY_NOW );
tor->verifyState = state;
tor->anyDate = tr_time( );
}
tr_torrent_activity
tr_torrentGetActivity( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
tr_torrentRecheckCompleteness( tor );
if( tor->verifyState == TR_VERIFY_NOW )
return TR_STATUS_CHECK;
if( tor->verifyState == TR_VERIFY_WAIT )
return TR_STATUS_CHECK_WAIT;
if( !tor->isRunning )
return TR_STATUS_STOPPED;
if( tor->completeness == TR_LEECH )
return TR_STATUS_DOWNLOAD;
return TR_STATUS_SEED;
}
static double
getVerifyProgress( const tr_torrent * tor )
{
tr_piece_index_t i, n;
tr_piece_index_t checked = 0;
assert( tr_isTorrent( tor ) );
for( i=0, n=tor->info.pieceCount; i!=n; ++i )
if( tor->info.pieces[i].timeChecked )
++checked;
return checked / (double)tor->info.pieceCount;
}
const tr_stat *
tr_torrentStat( tr_torrent * tor )
{
tr_stat * s;
uint64_t now;
uint64_t seedRatioBytesLeft;
uint64_t seedRatioBytesGoal;
bool seedRatioApplies;
uint16_t seedIdleMinutes;
if( !tor )
return NULL;
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
tor->lastStatTime = tr_time( );
s = &tor->stats;
s->id = tor->uniqueId;
s->activity = tr_torrentGetActivity( tor );
s->error = tor->error;
tr_strlcpy( s->errorString, tor->errorString, sizeof( s->errorString ) );
s->manualAnnounceTime = tr_announcerNextManualAnnounce( tor );
tr_peerMgrTorrentStats( tor,
&s->peersConnected,
&s->webseedsSendingToUs,
&s->peersSendingToUs,
&s->peersGettingFromUs,
s->peersFrom );
now = tr_time_msec( );
s->rawUploadSpeed_KBps = toSpeedKBps( tr_bandwidthGetRawSpeed_Bps ( &tor->bandwidth, now, TR_UP ) );
s->pieceUploadSpeed_KBps = toSpeedKBps( tr_bandwidthGetPieceSpeed_Bps( &tor->bandwidth, now, TR_UP ) );
s->rawDownloadSpeed_KBps = toSpeedKBps( tr_bandwidthGetRawSpeed_Bps ( &tor->bandwidth, now, TR_DOWN ) );
s->pieceDownloadSpeed_KBps = toSpeedKBps( tr_bandwidthGetPieceSpeed_Bps( &tor->bandwidth, now, TR_DOWN ) );
s->percentComplete = tr_cpPercentComplete ( &tor->completion );
s->metadataPercentComplete = tr_torrentGetMetadataPercent( tor );
s->percentDone = tr_cpPercentDone ( &tor->completion );
s->leftUntilDone = tr_cpLeftUntilDone( &tor->completion );
s->sizeWhenDone = tr_cpSizeWhenDone ( &tor->completion );
s->recheckProgress = s->activity == TR_STATUS_CHECK ? getVerifyProgress( tor ) : 0;
s->activityDate = tor->activityDate;
s->addedDate = tor->addedDate;
s->doneDate = tor->doneDate;
s->startDate = tor->startDate;
s->secondsSeeding = tor->secondsSeeding;
s->secondsDownloading = tor->secondsDownloading;
if ((s->activity == TR_STATUS_DOWNLOAD || s->activity == TR_STATUS_SEED) && s->startDate != 0)
s->idleSecs = difftime(tr_time(), MAX(s->startDate, s->activityDate));
else
s->idleSecs = -1;
s->corruptEver = tor->corruptCur + tor->corruptPrev;
s->downloadedEver = tor->downloadedCur + tor->downloadedPrev;
s->uploadedEver = tor->uploadedCur + tor->uploadedPrev;
s->haveValid = tr_cpHaveValid( &tor->completion );
s->haveUnchecked = tr_cpHaveTotal( &tor->completion ) - s->haveValid;
s->desiredAvailable = tr_peerMgrGetDesiredAvailable( tor );
s->ratio = tr_getRatio( s->uploadedEver,
s->downloadedEver ? s->downloadedEver : s->haveValid );
seedRatioApplies = tr_torrentGetSeedRatioBytes( tor, &seedRatioBytesLeft,
&seedRatioBytesGoal );
switch( s->activity )
{
/* etaXLSpeed exists because if we use the piece speed directly,
* brief fluctuations cause the ETA to jump all over the place.
* so, etaXLSpeed is a smoothed-out version of the piece speed
* to dampen the effect of fluctuations */
case TR_STATUS_DOWNLOAD:
if( ( tor->etaDLSpeedCalculatedAt + 800 ) < now ) {
tor->etaDLSpeed_KBps = ( ( tor->etaDLSpeedCalculatedAt + 4000 ) < now )
? s->pieceDownloadSpeed_KBps /* if no recent previous speed, no need to smooth */
: ((tor->etaDLSpeed_KBps*4.0) + s->pieceDownloadSpeed_KBps)/5.0; /* smooth across 5 readings */
tor->etaDLSpeedCalculatedAt = now;
}
if( s->leftUntilDone > s->desiredAvailable )
s->eta = TR_ETA_NOT_AVAIL;
else if( tor->etaDLSpeed_KBps < 1 )
s->eta = TR_ETA_UNKNOWN;
else
s->eta = s->leftUntilDone / toSpeedBytes(tor->etaDLSpeed_KBps);
s->etaIdle = TR_ETA_NOT_AVAIL;
break;
case TR_STATUS_SEED: {
if( !seedRatioApplies )
s->eta = TR_ETA_NOT_AVAIL;
else {
if( ( tor->etaULSpeedCalculatedAt + 800 ) < now ) {
tor->etaULSpeed_KBps = ( ( tor->etaULSpeedCalculatedAt + 4000 ) < now )
? s->pieceUploadSpeed_KBps /* if no recent previous speed, no need to smooth */
: ((tor->etaULSpeed_KBps*4.0) + s->pieceUploadSpeed_KBps)/5.0; /* smooth across 5 readings */
tor->etaULSpeedCalculatedAt = now;
}
if( tor->etaULSpeed_KBps < 1 )
s->eta = TR_ETA_UNKNOWN;
else
s->eta = seedRatioBytesLeft / toSpeedBytes(tor->etaULSpeed_KBps);
}
if( tor->etaULSpeed_KBps < 1 && tr_torrentGetSeedIdle( tor, &seedIdleMinutes ) )
s->etaIdle = seedIdleMinutes * 60 - s->idleSecs;
else
s->etaIdle = TR_ETA_NOT_AVAIL;
break;
}
default:
s->eta = TR_ETA_NOT_AVAIL;
s->etaIdle = TR_ETA_NOT_AVAIL;
break;
}
/* s->haveValid is here to make sure a torrent isn't marked 'finished'
* when the user hits "uncheck all" prior to starting the torrent... */
s->finished = tor->finishedSeedingByIdle || (seedRatioApplies && !seedRatioBytesLeft && s->haveValid);
if( !seedRatioApplies || s->finished )
s->seedRatioPercentDone = 1;
else if( !seedRatioBytesGoal ) /* impossible? safeguard for div by zero */
s->seedRatioPercentDone = 0;
else
s->seedRatioPercentDone = (double)(seedRatioBytesGoal - seedRatioBytesLeft) / seedRatioBytesGoal;
tr_torrentUnlock( tor );
/* test some of the constraints */
assert( s->sizeWhenDone <= tor->info.totalSize );
assert( s->leftUntilDone <= s->sizeWhenDone );
assert( s->desiredAvailable <= s->leftUntilDone );
return s;
}
/***
****
***/
static uint64_t
fileBytesCompleted( const tr_torrent * tor, tr_file_index_t index )
{
uint64_t total = 0;
const tr_file * f = &tor->info.files[index];
if( f->length )
{
tr_block_index_t first;
tr_block_index_t last;
tr_torGetFileBlockRange( tor, index, &first, &last );
if( first == last )
{
if( tr_cpBlockIsComplete( &tor->completion, first ) )
total = f->length;
}
else
{
/* the first block */
if( tr_cpBlockIsComplete( &tor->completion, first ) )
total += tor->blockSize - ( f->offset % tor->blockSize );
/* the middle blocks */
if( first + 1 < last ) {
uint64_t u = tr_bitfieldCountRange( &tor->completion.blockBitfield, first+1, last );
u *= tor->blockSize;
total += u;
}
/* the last block */
if( tr_cpBlockIsComplete( &tor->completion, last ) )
total += ( f->offset + f->length ) - ( (uint64_t)tor->blockSize * last );
}
}
return total;
}
tr_file_stat *
tr_torrentFiles( const tr_torrent * tor,
tr_file_index_t * fileCount )
{
tr_file_index_t i;
const tr_file_index_t n = tor->info.fileCount;
tr_file_stat * files = tr_new0( tr_file_stat, n );
tr_file_stat * walk = files;
const bool isSeed = tor->completeness == TR_SEED;
assert( tr_isTorrent( tor ) );
for( i=0; i<n; ++i, ++walk ) {
const uint64_t b = isSeed ? tor->info.files[i].length : fileBytesCompleted( tor, i );
walk->bytesCompleted = b;
walk->progress = tor->info.files[i].length > 0 ? ( (float)b / tor->info.files[i].length ) : 1.0f;
}
if( fileCount )
*fileCount = n;
return files;
}
void
tr_torrentFilesFree( tr_file_stat * files,
tr_file_index_t fileCount UNUSED )
{
tr_free( files );
}
/***
****
***/
double*
tr_torrentWebSpeeds_KBps( const tr_torrent * tor )
{
double * ret = NULL;
if( tr_isTorrent( tor ) )
{
tr_torrentLock( tor );
ret = tr_peerMgrWebSpeeds_KBps( tor );
tr_torrentUnlock( tor );
}
return ret;
}
tr_peer_stat *
tr_torrentPeers( const tr_torrent * tor, int * peerCount )
{
tr_peer_stat * ret = NULL;
if( tr_isTorrent( tor ) )
{
tr_torrentLock( tor );
ret = tr_peerMgrPeerStats( tor, peerCount );
tr_torrentUnlock( tor );
}
return ret;
}
void
tr_torrentPeersFree( tr_peer_stat * peers, int peerCount UNUSED )
{
tr_free( peers );
}
tr_tracker_stat *
tr_torrentTrackers( const tr_torrent * torrent, int * setmeTrackerCount )
{
tr_tracker_stat * ret = NULL;
if( tr_isTorrent( torrent ) )
{
tr_torrentLock( torrent );
ret = tr_announcerStats( torrent, setmeTrackerCount );
tr_torrentUnlock( torrent );
}
return ret;
}
void
tr_torrentTrackersFree( tr_tracker_stat * trackers, int trackerCount )
{
tr_announcerStatsFree( trackers, trackerCount );
}
void
tr_torrentAvailability( const tr_torrent * tor, int8_t * tab, int size )
{
if( tr_isTorrent( tor ) && ( tab != NULL ) && ( size > 0 ) )
{
tr_torrentLock( tor );
tr_peerMgrTorrentAvailability( tor, tab, size );
tr_torrentUnlock( tor );
}
}
void
tr_torrentAmountFinished( const tr_torrent * tor,
float * tab,
int size )
{
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
tr_cpGetAmountDone( &tor->completion, tab, size );
tr_torrentUnlock( tor );
}
static void
tr_torrentResetTransferStats( tr_torrent * tor )
{
tr_torrentLock( tor );
tor->downloadedPrev += tor->downloadedCur;
tor->downloadedCur = 0;
tor->uploadedPrev += tor->uploadedCur;
tor->uploadedCur = 0;
tor->corruptPrev += tor->corruptCur;
tor->corruptCur = 0;
tr_torrentSetDirty( tor );
tr_torrentUnlock( tor );
}
void
tr_torrentSetHasPiece( tr_torrent * tor,
tr_piece_index_t pieceIndex,
bool has )
{
assert( tr_isTorrent( tor ) );
assert( pieceIndex < tor->info.pieceCount );
if( has )
tr_cpPieceAdd( &tor->completion, pieceIndex );
else
tr_cpPieceRem( &tor->completion, pieceIndex );
}
/***
****
***/
static void
freeTorrent( tr_torrent * tor )
{
tr_torrent * t;
tr_session * session = tor->session;
tr_info * inf = &tor->info;
assert( !tor->isRunning );
tr_sessionLock( session );
tr_peerMgrRemoveTorrent( tor );
tr_cpDestruct( &tor->completion );
tr_announcerRemoveTorrent( session->announcer, tor );
tr_free( tor->downloadDir );
tr_free( tor->incompleteDir );
if( tor == session->torrentList )
session->torrentList = tor->next;
else for( t = session->torrentList; t != NULL; t = t->next ) {
if( t->next == tor ) {
t->next = tor->next;
break;
}
}
assert( session->torrentCount >= 1 );
session->torrentCount--;
tr_bandwidthDestruct( &tor->bandwidth );
tr_metainfoFree( inf );
tr_free( tor );
tr_sessionUnlock( session );
}
/**
*** Start/Stop Callback
**/
static void
torrentStartImpl( void * vtor )
{
time_t now;
tr_torrent * tor = vtor;
assert( tr_isTorrent( tor ) );
tr_sessionLock( tor->session );
tr_torrentRecheckCompleteness( tor );
now = tr_time( );
tor->isRunning = true;
tor->completeness = tr_cpGetStatus( &tor->completion );
tor->startDate = tor->anyDate = now;
tr_torrentClearError( tor );
tor->finishedSeedingByIdle = false;
tr_torrentResetTransferStats( tor );
tr_announcerTorrentStarted( tor );
tor->dhtAnnounceAt = now + tr_cryptoWeakRandInt( 20 );
tor->dhtAnnounce6At = now + tr_cryptoWeakRandInt( 20 );
tor->lpdAnnounceAt = now;
tr_peerMgrStartTorrent( tor );
tr_sessionUnlock( tor->session );
}
uint64_t
tr_torrentGetCurrentSizeOnDisk( const tr_torrent * tor )
{
tr_file_index_t i;
uint64_t byte_count = 0;
const tr_file_index_t n = tor->info.fileCount;
for( i=0; i<n; ++i )
{
struct stat sb;
char * filename = tr_torrentFindFile( tor, i );
sb.st_size = 0;
if( filename && !stat( filename, &sb ) )
byte_count += sb.st_size;
tr_free( filename );
}
return byte_count;
}
static void
torrentStart( tr_torrent * tor )
{
/* already running... */
if( tor->isRunning )
return;
/* don't allow the torrent to be started if the files disappeared */
if( setLocalErrorIfFilesDisappeared( tor ) )
return;
/* verifying right now... wait until that's done so
* we'll know what completeness to use/announce */
if( tor->verifyState != TR_VERIFY_NONE ) {
tor->startAfterVerify = true;
return;
}
/* otherwise, start it now... */
tr_sessionLock( tor->session );
/* allow finished torrents to be resumed */
if( tr_torrentIsSeedRatioDone( tor ) ) {
tr_torinf( tor, _( "Restarted manually -- disabling its seed ratio" ) );
tr_torrentSetRatioMode( tor, TR_RATIOLIMIT_UNLIMITED );
}
/* corresponds to the peer_id sent as a tracker request parameter.
* one tracker admin says: "When the same torrent is opened and
* closed and opened again without quitting Transmission ...
* change the peerid. It would help sometimes if a stopped event
* was missed to ensure that we didn't think someone was cheating. */
tr_peerIdInit( tor->peer_id );
tor->isRunning = 1;
tr_torrentSetDirty( tor );
tr_runInEventThread( tor->session, torrentStartImpl, tor );
tr_sessionUnlock( tor->session );
}
void
tr_torrentStart( tr_torrent * tor )
{
if( tr_isTorrent( tor ) )
torrentStart( tor );
}
static void
torrentRecheckDoneImpl( void * vtor )
{
tr_torrent * tor = vtor;
assert( tr_isTorrent( tor ) );
tr_torrentRecheckCompleteness( tor );
if( tor->startAfterVerify ) {
tor->startAfterVerify = false;
torrentStart( tor );
}
}
static void
torrentRecheckDoneCB( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
tr_runInEventThread( tor->session, torrentRecheckDoneImpl, tor );
}
static void
verifyTorrent( void * vtor )
{
tr_torrent * tor = vtor;
tr_sessionLock( tor->session );
/* if the torrent's already being verified, stop it */
tr_verifyRemove( tor );
/* if the torrent's running, stop it & set the restart-after-verify flag */
if( tor->startAfterVerify || tor->isRunning ) {
/* don't clobber isStopping */
const bool startAfter = tor->isStopping ? false : true;
tr_torrentStop( tor );
tor->startAfterVerify = startAfter;
}
if( setLocalErrorIfFilesDisappeared( tor ) )
tor->startAfterVerify = false;
else
tr_verifyAdd( tor, torrentRecheckDoneCB );
tr_sessionUnlock( tor->session );
}
void
tr_torrentVerify( tr_torrent * tor )
{
if( tr_isTorrent( tor ) )
tr_runInEventThread( tor->session, verifyTorrent, tor );
}
void
tr_torrentSave( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
if( tor->isDirty )
{
tor->isDirty = false;
tr_torrentSaveResume( tor );
}
}
static void
stopTorrent( void * vtor )
{
tr_torrent * tor = vtor;
tr_torinf( tor, "Pausing" );
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
tr_verifyRemove( tor );
tr_peerMgrStopTorrent( tor );
tr_announcerTorrentStopped( tor );
tr_cacheFlushTorrent( tor->session->cache, tor );
tr_fdTorrentClose( tor->session, tor->uniqueId );
if( !tor->isDeleting )
tr_torrentSave( tor );
tr_torrentUnlock( tor );
}
void
tr_torrentStop( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
if( tr_isTorrent( tor ) )
{
tr_sessionLock( tor->session );
tor->isRunning = 0;
tor->isStopping = 0;
tr_torrentSetDirty( tor );
tr_runInEventThread( tor->session, stopTorrent, tor );
tr_sessionUnlock( tor->session );
}
}
static void
closeTorrent( void * vtor )
{
tr_benc * d;
tr_torrent * tor = vtor;
assert( tr_isTorrent( tor ) );
d = tr_bencListAddDict( &tor->session->removedTorrents, 2 );
tr_bencDictAddInt( d, "id", tor->uniqueId );
tr_bencDictAddInt( d, "date", tr_time( ) );
tr_torinf( tor, "%s", _( "Removing torrent" ) );
stopTorrent( tor );
if( tor->isDeleting )
{
tr_metainfoRemoveSaved( tor->session, &tor->info );
tr_torrentRemoveResume( tor );
}
tor->isRunning = 0;
freeTorrent( tor );
}
void
tr_torrentFree( tr_torrent * tor )
{
if( tr_isTorrent( tor ) )
{
tr_session * session = tor->session;
assert( tr_isSession( session ) );
tr_sessionLock( session );
tr_torrentClearCompletenessCallback( tor );
tr_runInEventThread( session, closeTorrent, tor );
tr_sessionUnlock( session );
}
}
struct remove_data
{
tr_torrent * tor;
bool deleteFlag;
tr_fileFunc * deleteFunc;
};
static void tr_torrentDeleteLocalData( tr_torrent *, tr_fileFunc );
static void
removeTorrent( void * vdata )
{
struct remove_data * data = vdata;
if( data->deleteFlag )
tr_torrentDeleteLocalData( data->tor, data->deleteFunc );
tr_torrentClearCompletenessCallback( data->tor );
closeTorrent( data->tor );
tr_free( data );
}
void
tr_torrentRemove( tr_torrent * tor,
bool deleteFlag,
tr_fileFunc deleteFunc )
{
struct remove_data * data;
assert( tr_isTorrent( tor ) );
tor->isDeleting = 1;
data = tr_new0( struct remove_data, 1 );
data->tor = tor;
data->deleteFlag = deleteFlag;
data->deleteFunc = deleteFunc;
tr_runInEventThread( tor->session, removeTorrent, data );
}
/**
*** Completeness
**/
static const char *
getCompletionString( int type )
{
switch( type )
{
/* Translators: this is a minor point that's safe to skip over, but FYI:
"Complete" and "Done" are specific, different terms in Transmission:
"Complete" means we've downloaded every file in the torrent.
"Done" means we're done downloading the files we wanted, but NOT all
that exist */
case TR_PARTIAL_SEED:
return _( "Done" );
case TR_SEED:
return _( "Complete" );
default:
return _( "Incomplete" );
}
}
static void
fireCompletenessChange( tr_torrent * tor,
tr_completeness status,
bool wasRunning )
{
assert( ( status == TR_LEECH )
|| ( status == TR_SEED )
|| ( status == TR_PARTIAL_SEED ) );
if( tor->completeness_func )
tor->completeness_func( tor, status, wasRunning,
tor->completeness_func_user_data );
}
void
tr_torrentSetCompletenessCallback( tr_torrent * tor,
tr_torrent_completeness_func func,
void * user_data )
{
assert( tr_isTorrent( tor ) );
tor->completeness_func = func;
tor->completeness_func_user_data = user_data;
}
void
tr_torrentClearCompletenessCallback( tr_torrent * torrent )
{
tr_torrentSetCompletenessCallback( torrent, NULL, NULL );
}
void
tr_torrentSetRatioLimitHitCallback( tr_torrent * tor,
tr_torrent_ratio_limit_hit_func func,
void * user_data )
{
assert( tr_isTorrent( tor ) );
tor->ratio_limit_hit_func = func;
tor->ratio_limit_hit_func_user_data = user_data;
}
void
tr_torrentClearRatioLimitHitCallback( tr_torrent * torrent )
{
tr_torrentSetRatioLimitHitCallback( torrent, NULL, NULL );
}
void
tr_torrentSetIdleLimitHitCallback( tr_torrent * tor,
tr_torrent_idle_limit_hit_func func,
void * user_data )
{
assert( tr_isTorrent( tor ) );
tor->idle_limit_hit_func = func;
tor->idle_limit_hit_func_user_data = user_data;
}
void
tr_torrentClearIdleLimitHitCallback( tr_torrent * torrent )
{
tr_torrentSetIdleLimitHitCallback( torrent, NULL, NULL );
}
static void
onSigCHLD( int i UNUSED )
{
waitpid( -1, NULL, WNOHANG );
}
static void
torrentCallScript( const tr_torrent * tor, const char * script )
{
char timeStr[128];
const time_t now = tr_time( );
tr_strlcpy( timeStr, ctime( &now ), sizeof( timeStr ) );
*strchr( timeStr,'\n' ) = '\0';
if( script && *script )
{
int i;
char * cmd[] = { tr_strdup( script ), NULL };
char * env[] = {
tr_strdup_printf( "TR_APP_VERSION=%s", SHORT_VERSION_STRING ),
tr_strdup_printf( "TR_TIME_LOCALTIME=%s", timeStr ),
tr_strdup_printf( "TR_TORRENT_DIR=%s", tor->currentDir ),
tr_strdup_printf( "TR_TORRENT_ID=%d", tr_torrentId( tor ) ),
tr_strdup_printf( "TR_TORRENT_HASH=%s", tor->info.hashString ),
tr_strdup_printf( "TR_TORRENT_NAME=%s", tr_torrentName( tor ) ),
NULL };
tr_torinf( tor, "Calling script \"%s\"", script );
#ifdef WIN32
_spawnvpe( _P_NOWAIT, script, (const char*)cmd, env );
#else
signal( SIGCHLD, onSigCHLD );
if( !fork( ) )
{
for (i=0; env[i]; ++i)
putenv(env[i]);
execvp( script, cmd );
_exit( 0 );
}
#endif
for( i=0; cmd[i]; ++i ) tr_free( cmd[i] );
for( i=0; env[i]; ++i ) tr_free( env[i] );
}
}
void
tr_torrentRecheckCompleteness( tr_torrent * tor )
{
tr_completeness completeness;
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
completeness = tr_cpGetStatus( &tor->completion );
if( completeness != tor->completeness )
{
const int recentChange = tor->downloadedCur != 0;
const bool wasLeeching = !tr_torrentIsSeed( tor );
const bool wasRunning = tor->isRunning;
if( recentChange )
{
tr_torinf( tor, _( "State changed from \"%1$s\" to \"%2$s\"" ),
getCompletionString( tor->completeness ),
getCompletionString( completeness ) );
}
tor->completeness = completeness;
tr_fdTorrentClose( tor->session, tor->uniqueId );
if( tr_torrentIsSeed( tor ) )
{
if( recentChange )
{
tr_announcerTorrentCompleted( tor );
tor->doneDate = tor->anyDate = tr_time( );
}
if( wasLeeching && wasRunning )
{
/* clear interested flag on all peers */
tr_peerMgrClearInterest( tor );
/* if completeness was TR_LEECH then the seed limit check will have been skipped in bandwidthPulse */
tr_torrentCheckSeedLimit( tor );
}
if( tor->currentDir == tor->incompleteDir )
tr_torrentSetLocation( tor, tor->downloadDir, true, NULL, NULL );
if( tr_sessionIsTorrentDoneScriptEnabled( tor->session ) )
torrentCallScript( tor, tr_sessionGetTorrentDoneScript( tor->session ) );
}
fireCompletenessChange( tor, completeness, wasRunning );
tr_torrentSetDirty( tor );
}
tr_torrentUnlock( tor );
}
/***
****
***/
static void
tr_torrentFireMetadataCompleted( tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
if( tor->metadata_func )
tor->metadata_func( tor, tor->metadata_func_user_data );
}
void
tr_torrentSetMetadataCallback( tr_torrent * tor,
tr_torrent_metadata_func func,
void * user_data )
{
assert( tr_isTorrent( tor ) );
tor->metadata_func = func;
tor->metadata_func_user_data = user_data;
}
/**
*** File priorities
**/
void
tr_torrentInitFilePriority( tr_torrent * tor,
tr_file_index_t fileIndex,
tr_priority_t priority )
{
tr_piece_index_t i;
tr_file * file;
assert( tr_isTorrent( tor ) );
assert( fileIndex < tor->info.fileCount );
assert( tr_isPriority( priority ) );
file = &tor->info.files[fileIndex];
file->priority = priority;
for( i = file->firstPiece; i <= file->lastPiece; ++i )
tor->info.pieces[i].priority = calculatePiecePriority( tor, i, fileIndex );
}
void
tr_torrentSetFilePriorities( tr_torrent * tor,
const tr_file_index_t * files,
tr_file_index_t fileCount,
tr_priority_t priority )
{
tr_file_index_t i;
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
for( i = 0; i < fileCount; ++i )
if( files[i] < tor->info.fileCount )
tr_torrentInitFilePriority( tor, files[i], priority );
tr_torrentSetDirty( tor );
tr_peerMgrRebuildRequests( tor );
tr_torrentUnlock( tor );
}
tr_priority_t*
tr_torrentGetFilePriorities( const tr_torrent * tor )
{
tr_file_index_t i;
tr_priority_t * p;
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
p = tr_new0( tr_priority_t, tor->info.fileCount );
for( i = 0; i < tor->info.fileCount; ++i )
p[i] = tor->info.files[i].priority;
tr_torrentUnlock( tor );
return p;
}
/**
*** File DND
**/
static void
setFileDND( tr_torrent * tor, tr_file_index_t fileIndex, int doDownload )
{
const int8_t dnd = !doDownload;
tr_piece_index_t firstPiece;
int8_t firstPieceDND;
tr_piece_index_t lastPiece;
int8_t lastPieceDND;
tr_file_index_t i;
tr_file * file = &tor->info.files[fileIndex];
file->dnd = dnd;
firstPiece = file->firstPiece;
lastPiece = file->lastPiece;
/* can't set the first piece to DND unless
every file using that piece is DND */
firstPieceDND = dnd;
if( fileIndex > 0 )
{
for( i = fileIndex - 1; firstPieceDND; --i )
{
if( tor->info.files[i].lastPiece != firstPiece )
break;
firstPieceDND = tor->info.files[i].dnd;
if( !i )
break;
}
}
/* can't set the last piece to DND unless
every file using that piece is DND */
lastPieceDND = dnd;
for( i = fileIndex + 1; lastPieceDND && i < tor->info.fileCount; ++i )
{
if( tor->info.files[i].firstPiece != lastPiece )
break;
lastPieceDND = tor->info.files[i].dnd;
}
if( firstPiece == lastPiece )
{
tor->info.pieces[firstPiece].dnd = firstPieceDND && lastPieceDND;
}
else
{
tr_piece_index_t pp;
tor->info.pieces[firstPiece].dnd = firstPieceDND;
tor->info.pieces[lastPiece].dnd = lastPieceDND;
for( pp = firstPiece + 1; pp < lastPiece; ++pp )
tor->info.pieces[pp].dnd = dnd;
}
}
void
tr_torrentInitFileDLs( tr_torrent * tor,
const tr_file_index_t * files,
tr_file_index_t fileCount,
bool doDownload )
{
tr_file_index_t i;
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
for( i=0; i<fileCount; ++i )
if( files[i] < tor->info.fileCount )
setFileDND( tor, files[i], doDownload );
tr_cpInvalidateDND( &tor->completion );
tr_torrentUnlock( tor );
}
void
tr_torrentSetFileDLs( tr_torrent * tor,
const tr_file_index_t * files,
tr_file_index_t fileCount,
bool doDownload )
{
assert( tr_isTorrent( tor ) );
tr_torrentLock( tor );
tr_torrentInitFileDLs( tor, files, fileCount, doDownload );
tr_torrentSetDirty( tor );
tr_peerMgrRebuildRequests( tor );
tr_torrentUnlock( tor );
}
/***
****
***/
tr_priority_t
tr_torrentGetPriority( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->bandwidth.priority;
}
void
tr_torrentSetPriority( tr_torrent * tor, tr_priority_t priority )
{
assert( tr_isTorrent( tor ) );
assert( tr_isPriority( priority ) );
if( tor->bandwidth.priority != priority )
{
tor->bandwidth.priority = priority;
tr_torrentSetDirty( tor );
}
}
/***
****
***/
void
tr_torrentSetPeerLimit( tr_torrent * tor,
uint16_t maxConnectedPeers )
{
assert( tr_isTorrent( tor ) );
if ( tor->maxConnectedPeers != maxConnectedPeers )
{
tor->maxConnectedPeers = maxConnectedPeers;
tr_torrentSetDirty( tor );
}
}
uint16_t
tr_torrentGetPeerLimit( const tr_torrent * tor )
{
assert( tr_isTorrent( tor ) );
return tor->maxConnectedPeers;
}
/***
****
***/
void
tr_torrentGetBlockLocation( const tr_torrent * tor,
tr_block_index_t block,
tr_piece_index_t * piece,
uint32_t * offset,
uint32_t * length )
{
uint64_t pos = block;
pos *= tor->blockSize;
*piece = pos / tor->info.pieceSize;
*offset = pos - ( *piece * tor->info.pieceSize );
*length = tr_torBlockCountBytes( tor, block );
}
tr_block_index_t
_tr_block( const tr_torrent * tor,
tr_piece_index_t index,
uint32_t offset )
{
tr_block_index_t ret;
assert( tr_isTorrent( tor ) );
ret = index;
ret *= ( tor->info.pieceSize / tor->blockSize );
ret += offset / tor->blockSize;
return ret;
}
bool
tr_torrentReqIsValid( const tr_torrent * tor,
tr_piece_index_t index,
uint32_t offset,
uint32_t length )
{
int err = 0;
assert( tr_isTorrent( tor ) );
if( index >= tor->info.pieceCount )
err = 1;
else if( length < 1 )
err = 2;
else if( ( offset + length ) > tr_torPieceCountBytes( tor, index ) )
err = 3;
else if( length > MAX_BLOCK_SIZE )
err = 4;
else if( tr_pieceOffset( tor, index, offset, length ) > tor->info.totalSize )
err = 5;
if( err ) tr_tordbg( tor, "index %lu offset %lu length %lu err %d\n",
(unsigned long)index,
(unsigned long)offset,
(unsigned long)length,
err );
return !err;
}
uint64_t
tr_pieceOffset( const tr_torrent * tor,
tr_piece_index_t index,
uint32_t offset,
uint32_t length )
{
uint64_t ret;
assert( tr_isTorrent( tor ) );
ret = tor->info.pieceSize;
ret *= index;
ret += offset;
ret += length;
return ret;
}
void
tr_torGetFileBlockRange( const tr_torrent * tor,
const tr_file_index_t file,
tr_block_index_t * first,
tr_block_index_t * last )
{
const tr_file * f = &tor->info.files[file];
uint64_t offset = f->offset;
*first = offset / tor->blockSize;
if( !f->length )
*last = *first;
else {
offset += f->length - 1;
*last = offset / tor->blockSize;
}
}
void
tr_torGetPieceBlockRange( const tr_torrent * tor,
const tr_piece_index_t piece,
tr_block_index_t * first,
tr_block_index_t * last )
{
uint64_t offset = tor->info.pieceSize;
offset *= piece;
*first = offset / tor->blockSize;
offset += ( tr_torPieceCountBytes( tor, piece ) - 1 );
*last = offset / tor->blockSize;
}
/***
****
***/
void
tr_torrentSetPieceChecked( tr_torrent * tor, tr_piece_index_t pieceIndex )
{
assert( tr_isTorrent( tor ) );
assert( pieceIndex < tor->info.pieceCount );
tor->info.pieces[pieceIndex].timeChecked = tr_time( );
}
void
tr_torrentSetChecked( tr_torrent * tor, time_t when )
{
tr_piece_index_t i, n;
assert( tr_isTorrent( tor ) );
for( i=0, n=tor->info.pieceCount; i!=n; ++i )
tor->info.pieces[i].timeChecked = when;
}
bool
tr_torrentCheckPiece( tr_torrent * tor, tr_piece_index_t pieceIndex )
{
const bool pass = tr_ioTestPiece( tor, pieceIndex );
tr_deeplog_tor( tor, "[LAZY] tr_torrentCheckPiece tested piece %zu, pass==%d", (size_t)pieceIndex, (int)pass );
tr_torrentSetHasPiece( tor, pieceIndex, pass );
tr_torrentSetPieceChecked( tor, pieceIndex );
tor->anyDate = tr_time( );
tr_torrentSetDirty( tor );
return pass;
}
time_t
tr_torrentGetFileMTime( const tr_torrent * tor, tr_file_index_t i )
{
time_t mtime = 0;
if( !tr_fdFileGetCachedMTime( tor->session, tor->uniqueId, i, &mtime ) )
tr_torrentFindFile2( tor, i, NULL, NULL, &mtime );
return mtime;
}
bool
tr_torrentPieceNeedsCheck( const tr_torrent * tor, tr_piece_index_t p )
{
uint64_t unused;
tr_file_index_t f;
const tr_info * inf = tr_torrentInfo( tor );
/* if we've never checked this piece, then it needs to be checked */
if( !inf->pieces[p].timeChecked )
return true;
/* If we think we've completed one of the files in this piece,
* but it's been modified since we last checked it,
* then it needs to be rechecked */
tr_ioFindFileLocation( tor, p, 0, &f, &unused );
for( ; f < inf->fileCount && pieceHasFile( p, &inf->files[f] ); ++f )
if( tr_cpFileIsComplete( &tor->completion, f ) )
if( tr_torrentGetFileMTime( tor, f ) > inf->pieces[p].timeChecked )
return true;
return false;
}
/***
****
***/
static int
compareTrackerByTier( const void * va, const void * vb )
{
const tr_tracker_info * a = va;
const tr_tracker_info * b = vb;
/* sort by tier */
if( a->tier != b->tier )
return a->tier - b->tier;
/* get the effects of a stable sort by comparing the two elements' addresses */
return a - b;
}
bool
tr_torrentSetAnnounceList( tr_torrent * tor,
const tr_tracker_info * trackers_in,
int trackerCount )
{
int i;
tr_benc metainfo;
bool ok = true;
tr_tracker_info * trackers;
tr_torrentLock( tor );
assert( tr_isTorrent( tor ) );
/* ensure the trackers' tiers are in ascending order */
trackers = tr_memdup( trackers_in, sizeof( tr_tracker_info ) * trackerCount );
qsort( trackers, trackerCount, sizeof( tr_tracker_info ), compareTrackerByTier );
/* look for bad URLs */
for( i=0; ok && i<trackerCount; ++i )
if( !tr_urlIsValidTracker( trackers[i].announce ) )
ok = false;
/* save to the .torrent file */
if( ok && !tr_bencLoadFile( &metainfo, TR_FMT_BENC, tor->info.torrent ) )
{
bool hasInfo;
tr_info tmpInfo;
/* remove the old fields */
tr_bencDictRemove( &metainfo, "announce" );
tr_bencDictRemove( &metainfo, "announce-list" );
/* add the new fields */
if( trackerCount > 0 )
{
tr_bencDictAddStr( &metainfo, "announce", trackers[0].announce );
}
if( trackerCount > 1 )
{
int i;
int prevTier = -1;
tr_benc * tier = NULL;
tr_benc * announceList = tr_bencDictAddList( &metainfo, "announce-list", 0 );
for( i=0; i<trackerCount; ++i ) {
if( prevTier != trackers[i].tier ) {
prevTier = trackers[i].tier;
tier = tr_bencListAddList( announceList, 0 );
}
tr_bencListAddStr( tier, trackers[i].announce );
}
}
/* try to parse it back again, to make sure it's good */
memset( &tmpInfo, 0, sizeof( tr_info ) );
if( tr_metainfoParse( tor->session, &metainfo, &tmpInfo,
&hasInfo, &tor->infoDictLength ) )
{
/* it's good, so keep these new trackers and free the old ones */
tr_info swap;
swap.trackers = tor->info.trackers;
swap.trackerCount = tor->info.trackerCount;
tor->info.trackers = tmpInfo.trackers;
tor->info.trackerCount = tmpInfo.trackerCount;
tmpInfo.trackers = swap.trackers;
tmpInfo.trackerCount = swap.trackerCount;
tr_metainfoFree( &tmpInfo );
tr_bencToFile( &metainfo, TR_FMT_BENC, tor->info.torrent );
}
/* cleanup */
tr_bencFree( &metainfo );
/* if we had a tracker-related error on this torrent,
* and that tracker's been removed,
* then clear the error */
if( ( tor->error == TR_STAT_TRACKER_WARNING )
|| ( tor->error == TR_STAT_TRACKER_ERROR ) )
{
bool clear = true;
for( i=0; clear && i<trackerCount; ++i )
if( !strcmp( trackers[i].announce, tor->errorTracker ) )
clear = false;
if( clear )
tr_torrentClearError( tor );
}
/* tell the announcer to reload this torrent's tracker list */
tr_announcerResetTorrent( tor->session->announcer, tor );
}
tr_torrentUnlock( tor );
tr_free( trackers );
return ok;
}
/**
***
**/
void
tr_torrentSetAddedDate( tr_torrent * tor,
time_t t )
{
assert( tr_isTorrent( tor ) );
tor->addedDate = t;
tor->anyDate = MAX( tor->anyDate, tor->addedDate );
}
void
tr_torrentSetActivityDate( tr_torrent * tor, time_t t )
{
assert( tr_isTorrent( tor ) );
tor->activityDate = t;
tor->anyDate = MAX( tor->anyDate, tor->activityDate );
}
void
tr_torrentSetDoneDate( tr_torrent * tor,
time_t t )
{
assert( tr_isTorrent( tor ) );
tor->doneDate = t;
tor->anyDate = MAX( tor->anyDate, tor->doneDate );
}
/**
***
**/
uint64_t
tr_torrentGetBytesLeftToAllocate( const tr_torrent * tor )
{
tr_file_index_t i;
uint64_t bytesLeft = 0;
assert( tr_isTorrent( tor ) );
for( i=0; i<tor->info.fileCount; ++i )
{
if( !tor->info.files[i].dnd )
{
struct stat sb;
const uint64_t length = tor->info.files[i].length;
char * path = tr_torrentFindFile( tor, i );
bytesLeft += length;
if( ( path != NULL ) && !stat( path, &sb )
&& S_ISREG( sb.st_mode )
&& ( (uint64_t)sb.st_size <= length ) )
bytesLeft -= sb.st_size;
tr_free( path );
}
}
return bytesLeft;
}
/****
***** Removing the torrent's local data
****/
static int
vstrcmp( const void * a, const void * b )
{
return strcmp( a, b );
}
static int
compareLongestFirst( const void * a, const void * b )
{
const size_t alen = strlen( a );
const size_t blen = strlen( b );
if( alen != blen )
return alen > blen ? -1 : 1;
return vstrcmp( a, b );
}
static void
addDirtyFile( const char * root,
const char * filename,
tr_ptrArray * dirtyFolders )
{
char * dir = tr_dirname( filename );
/* add the parent folders to dirtyFolders until we reach the root or a known-dirty */
while ( ( dir != NULL )
&& ( strlen( root ) <= strlen( dir ) )
&& ( tr_ptrArrayFindSorted( dirtyFolders, dir, vstrcmp ) == NULL ) )
{
char * tmp;
tr_ptrArrayInsertSorted( dirtyFolders, tr_strdup( dir ), vstrcmp );
tmp = tr_dirname( dir );
tr_free( dir );
dir = tmp;
}
tr_free( dir );
}
static void
walkLocalData( const tr_torrent * tor,
const char * root,
const char * dir,
const char * base,
tr_ptrArray * torrentFiles,
tr_ptrArray * folders,
tr_ptrArray * dirtyFolders )
{
struct stat sb;
char * buf = tr_buildPath( dir, base, NULL );
int i = stat( buf, &sb );
if( !i )
{
DIR * odir = NULL;
if( S_ISDIR( sb.st_mode ) && ( ( odir = opendir ( buf ) ) ) )
{
struct dirent *d;
tr_ptrArrayInsertSorted( folders, tr_strdup( buf ), vstrcmp );
for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
if( d->d_name && strcmp( d->d_name, "." ) && strcmp( d->d_name, ".." ) )
walkLocalData( tor, root, buf, d->d_name, torrentFiles, folders, dirtyFolders );
closedir( odir );
}
else if( S_ISREG( sb.st_mode ) && ( sb.st_size > 0 ) )
{
const char * sub = buf + strlen( tor->currentDir ) + strlen( TR_PATH_DELIMITER_STR );
const bool isTorrentFile = tr_ptrArrayFindSorted( torrentFiles, sub, vstrcmp ) != NULL;
if( !isTorrentFile )
addDirtyFile( root, buf, dirtyFolders );
}
}
tr_free( buf );
}
static void
deleteLocalFile( const char * filename, tr_fileFunc fileFunc )
{
struct stat sb;
if( !stat( filename, &sb ) ) /* if file exists... */
fileFunc( filename );
}
static void
deleteLocalData( tr_torrent * tor, tr_fileFunc fileFunc )
{
int i, n;
char ** s;
tr_file_index_t f;
tr_ptrArray torrentFiles = TR_PTR_ARRAY_INIT;
tr_ptrArray folders = TR_PTR_ARRAY_INIT;
tr_ptrArray dirtyFolders = TR_PTR_ARRAY_INIT; /* dirty == contains non-torrent files */
const char * firstFile = tor->info.files[0].name;
const char * cpch = strchr( firstFile, TR_PATH_DELIMITER );
char * tmp = cpch ? tr_strndup( firstFile, cpch - firstFile ) : NULL;
char * root = tr_buildPath( tor->currentDir, tmp, NULL );
for( f=0; f<tor->info.fileCount; ++f ) {
tr_ptrArrayInsertSorted( &torrentFiles, tr_strdup( tor->info.files[f].name ), vstrcmp );
tr_ptrArrayInsertSorted( &torrentFiles, tr_torrentBuildPartial( tor, f ), vstrcmp );
}
/* build the set of folders and dirtyFolders */
walkLocalData( tor, root, root, NULL, &torrentFiles, &folders, &dirtyFolders );
/* try to remove entire folders first, so that the recycle bin will be tidy */
s = (char**) tr_ptrArrayPeek( &folders, &n );
for( i=0; i<n; ++i )
if( tr_ptrArrayFindSorted( &dirtyFolders, s[i], vstrcmp ) == NULL )
deleteLocalFile( s[i], fileFunc );
/* now blow away any remaining torrent files, such as torrent files in dirty folders */
for( i=0, n=tr_ptrArraySize( &torrentFiles ); i<n; ++i ) {
char * path = tr_buildPath( tor->currentDir, tr_ptrArrayNth( &torrentFiles, i ), NULL );
deleteLocalFile( path, fileFunc );
tr_free( path );
}
/* Now clean out the directories left empty from the previous step.
* Work from deepest to shallowest s.t. lower folders
* won't prevent the upper folders from being deleted */
{
tr_ptrArray cleanFolders = TR_PTR_ARRAY_INIT;
s = (char**) tr_ptrArrayPeek( &folders, &n );
for( i=0; i<n; ++i )
if( tr_ptrArrayFindSorted( &dirtyFolders, s[i], vstrcmp ) == NULL )
tr_ptrArrayInsertSorted( &cleanFolders, s[i], compareLongestFirst );
s = (char**) tr_ptrArrayPeek( &cleanFolders, &n );
for( i=0; i<n; ++i ) {
#ifdef SYS_DARWIN
char * dsStore = tr_buildPath( s[i], ".DS_Store", NULL );
deleteLocalFile( dsStore, fileFunc );
tr_free( dsStore );
#endif
deleteLocalFile( s[i], fileFunc );
}
tr_ptrArrayDestruct( &cleanFolders, NULL );
}
/* cleanup */
tr_ptrArrayDestruct( &dirtyFolders, tr_free );
tr_ptrArrayDestruct( &folders, tr_free );
tr_ptrArrayDestruct( &torrentFiles, tr_free );
tr_free( root );
tr_free( tmp );
}
static void
tr_torrentDeleteLocalData( tr_torrent * tor, tr_fileFunc fileFunc )
{
assert( tr_isTorrent( tor ) );
if( fileFunc == NULL )
fileFunc = remove;
/* close all the files because we're about to delete them */
tr_cacheFlushTorrent( tor->session->cache, tor );
tr_fdTorrentClose( tor->session, tor->uniqueId );
if( tor->info.fileCount > 1 )
{
deleteLocalData( tor, fileFunc );
}
else if( tor->info.fileCount == 1 )
{
char * tmp;
/* torrent only has one file */
char * path = tr_buildPath( tor->currentDir, tor->info.files[0].name, NULL );
deleteLocalFile( path, fileFunc );
tr_free( path );
tmp = tr_torrentBuildPartial( tor, 0 );
path = tr_buildPath( tor->currentDir, tmp, NULL );
deleteLocalFile( path, fileFunc );
tr_free( path );
tr_free( tmp );
}
}
/***
****
***/
struct LocationData
{
bool move_from_old_location;
volatile int * setme_state;
volatile double * setme_progress;
char * location;
tr_torrent * tor;
};
static void
setLocation( void * vdata )
{
bool err = false;
struct LocationData * data = vdata;
tr_torrent * tor = data->tor;
const bool do_move = data->move_from_old_location;
const char * location = data->location;
double bytesHandled = 0;
assert( tr_isTorrent( tor ) );
tr_dbg( "Moving \"%s\" location from currentDir \"%s\" to \"%s\"",
tr_torrentName(tor), tor->currentDir, location );
tr_mkdirp( location, 0777 );
if( !tr_is_same_file( location, tor->currentDir ) )
{
tr_file_index_t i;
/* bad idea to move files while they're being verified... */
tr_verifyRemove( tor );
/* try to move the files.
* FIXME: there are still all kinds of nasty cases, like what
* if the target directory runs out of space halfway through... */
for( i=0; !err && i<tor->info.fileCount; ++i )
{
const tr_file * f = &tor->info.files[i];
const char * oldbase;
char * sub;
if( tr_torrentFindFile2( tor, i, &oldbase, &sub, NULL ) )
{
char * oldpath = tr_buildPath( oldbase, sub, NULL );
char * newpath = tr_buildPath( location, sub, NULL );
tr_dbg( "Found file #%d: %s", (int)i, oldpath );
if( do_move && !tr_is_same_file( oldpath, newpath ) )
{
bool renamed = false;
errno = 0;
tr_torinf( tor, "moving \"%s\" to \"%s\"", oldpath, newpath );
if( tr_moveFile( oldpath, newpath, &renamed ) )
{
err = true;
tr_torerr( tor, "error moving \"%s\" to \"%s\": %s",
oldpath, newpath, tr_strerror( errno ) );
}
}
tr_free( newpath );
tr_free( oldpath );
tr_free( sub );
}
if( data->setme_progress )
{
bytesHandled += f->length;
*data->setme_progress = bytesHandled / tor->info.totalSize;
}
}
if( !err )
{
/* blow away the leftover subdirectories in the old location */
if( do_move )
tr_torrentDeleteLocalData( tor, remove );
/* set the new location and reverify */
tr_torrentSetDownloadDir( tor, location );
}
}
if( !err && do_move )
{
tr_free( tor->incompleteDir );
tor->incompleteDir = NULL;
tor->currentDir = tor->downloadDir;
}
if( data->setme_state )
*data->setme_state = err ? TR_LOC_ERROR : TR_LOC_DONE;
/* cleanup */
tr_free( data->location );
tr_free( data );
}
void
tr_torrentSetLocation( tr_torrent * tor,
const char * location,
bool move_from_old_location,
volatile double * setme_progress,
volatile int * setme_state )
{
struct LocationData * data;
assert( tr_isTorrent( tor ) );
if( setme_state )
*setme_state = TR_LOC_MOVING;
if( setme_progress )
*setme_progress = 0;
/* run this in the libtransmission thread */
data = tr_new( struct LocationData, 1 );
data->tor = tor;
data->location = tr_strdup( location );
data->move_from_old_location = move_from_old_location;
data->setme_state = setme_state;
data->setme_progress = setme_progress;
tr_runInEventThread( tor->session, setLocation, data );
}
/***
****
***/
void
tr_torrentFileCompleted( tr_torrent * tor, tr_file_index_t fileNum )
{
char * sub;
const char * base;
const tr_info * inf = &tor->info;
const tr_file * f = &inf->files[fileNum];
tr_piece * p;
const tr_piece * pend;
const time_t now = tr_time( );
/* close the file so that we can reopen in read-only mode as needed */
tr_fdFileClose( tor->session, tor, fileNum );
/* 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 */
for( p=&inf->pieces[f->firstPiece], pend=&inf->pieces[f->lastPiece]; p!=pend; ++p )
p->timeChecked = now;
/* if the torrent's current filename isn't the same as the one in the
* metadata -- for example, if it had the ".part" suffix appended to
* it until now -- then rename it to match the one in the metadata */
if( tr_torrentFindFile2( tor, fileNum, &base, &sub, NULL ) )
{
if( strcmp( sub, f->name ) )
{
char * oldpath = tr_buildPath( base, sub, NULL );
char * newpath = tr_buildPath( base, f->name, NULL );
if( rename( oldpath, newpath ) )
tr_torerr( tor, "Error moving \"%s\" to \"%s\": %s", oldpath, newpath, tr_strerror( errno ) );
tr_free( newpath );
tr_free( oldpath );
}
tr_free( sub );
}
}
/***
****
***/
#ifdef SYS_DARWIN
#define TR_STAT_MTIME(sb) ((sb).st_mtimespec.tv_sec)
#else
#define TR_STAT_MTIME(sb) ((sb).st_mtime)
#endif
static bool
fileExists( const char * filename, time_t * mtime )
{
struct stat sb;
const bool ok = !stat( filename, &sb );
if( ok && ( mtime != NULL ) )
*mtime = TR_STAT_MTIME( sb );
return ok;
}
bool
tr_torrentFindFile2( const tr_torrent * tor, tr_file_index_t fileNum,
const char ** base, char ** subpath, time_t * mtime )
{
char * part = NULL;
const tr_file * file;
const char * b = NULL;
const char * s = NULL;
assert( tr_isTorrent( tor ) );
assert( fileNum < tor->info.fileCount );
file = &tor->info.files[fileNum];
if( b == NULL ) {
char * filename = tr_buildPath( tor->downloadDir, file->name, NULL );
if( fileExists( filename, mtime ) ) {
b = tor->downloadDir;
s = file->name;
}
tr_free( filename );
}
if( ( b == NULL ) && ( tor->incompleteDir != NULL ) ) {
char * filename = tr_buildPath( tor->incompleteDir, file->name, NULL );
if( fileExists( filename, mtime ) ) {
b = tor->incompleteDir;
s = file->name;
}
tr_free( filename );
}
if( b == NULL )
part = tr_torrentBuildPartial( tor, fileNum );
if( ( b == NULL ) && ( tor->incompleteDir != NULL ) ) {
char * filename = tr_buildPath( tor->incompleteDir, part, NULL );
if( fileExists( filename, mtime ) ) {
b = tor->incompleteDir;
s = part;
}
tr_free( filename );
}
if( b == NULL) {
char * filename = tr_buildPath( tor->downloadDir, part, NULL );
if( fileExists( filename, mtime ) ) {
b = tor->downloadDir;
s = part;
}
tr_free( filename );
}
if( base != NULL )
*base = b;
if( subpath != NULL )
*subpath = tr_strdup( s );
tr_free( part );
return b != NULL;
}
char*
tr_torrentFindFile( const tr_torrent * tor, tr_file_index_t fileNum )
{
char * subpath;
char * ret = NULL;
const char * base;
if( tr_torrentFindFile2( tor, fileNum, &base, &subpath, NULL ) )
{
ret = tr_buildPath( base, subpath, NULL );
tr_free( subpath );
}
return ret;
}
/* Decide whether we should be looking for files in downloadDir or incompleteDir. */
static void
refreshCurrentDir( tr_torrent * tor )
{
const char * dir = NULL;
if( tor->incompleteDir == NULL )
dir = tor->downloadDir;
else if( !tr_torrentHasMetadata( tor ) ) /* no files to find */
dir = tor->incompleteDir;
else if( !tr_torrentFindFile2( tor, 0, &dir, NULL, NULL ) )
dir = tor->incompleteDir;
assert( dir != NULL );
assert( ( dir == tor->downloadDir ) || ( dir == tor->incompleteDir ) );
tor->currentDir = dir;
}
char*
tr_torrentBuildPartial( const tr_torrent * tor, tr_file_index_t fileNum )
{
return tr_strdup_printf( "%s.part", tor->info.files[fileNum].name );
}