1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-27 18:18:10 +00:00
transmission/libtransmission/session.c
Jordan Lee 879a2afcbd Update the copyright year in the source code comments.
The Berne Convention says that the copyright year is moot, so instead of adding another year to each file as in previous years, I've removed the year altogether from the source code comments in libtransmission, gtk, qt, utils, daemon, and cli.

Juliusz's copyright notice in tr-dht and Johannes' copyright notice in tr-lpd have been left alone; it didn't seem appropriate to modify them.
2011-01-19 13:48:47 +00:00

2579 lines
71 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 <assert.h>
#include <errno.h> /* ENOENT */
#include <stdlib.h>
#include <string.h> /* memcpy */
#include <signal.h>
#include <sys/types.h> /* stat(), umask() */
#include <sys/stat.h> /* stat(), umask() */
#include <unistd.h> /* stat */
#include <dirent.h> /* opendir */
#include <event2/event.h>
//#define TR_SHOW_DEPRECATED
#include "transmission.h"
#include "announcer.h"
#include "bandwidth.h"
#include "bencode.h"
#include "blocklist.h"
#include "cache.h"
#include "crypto.h"
#include "fdlimit.h"
#include "list.h"
#include "metainfo.h" /* tr_metainfoFree */
#include "net.h"
#include "peer-io.h"
#include "peer-mgr.h"
#include "platform.h" /* tr_lock */
#include "port-forwarding.h"
#include "rpc-server.h"
#include "session.h"
#include "stats.h"
#include "torrent.h"
#include "tr-udp.h"
#include "tr-lpd.h"
#include "trevent.h"
#include "utils.h"
#include "verify.h"
#include "version.h"
#include "web.h"
enum
{
SAVE_INTERVAL_SECS = 360,
DEFAULT_CACHE_SIZE_MB = 4
};
#define dbgmsg( ... ) \
do { \
if( tr_deepLoggingIsActive( ) ) \
tr_deepLog( __FILE__, __LINE__, NULL, __VA_ARGS__ ); \
} while( 0 )
static tr_port
getRandomPort( tr_session * s )
{
return tr_cryptoWeakRandInt( s->randomPortHigh - s->randomPortLow + 1) + s->randomPortLow;
}
/* Generate a peer id : "-TRxyzb-" + 12 random alphanumeric
characters, where x is the major version number, y is the
minor version number, z is the maintenance number, and b
designates beta (Azureus-style) */
uint8_t*
tr_peerIdNew( void )
{
int i;
int val;
int total = 0;
uint8_t * buf = tr_new( uint8_t, 21 );
const char * pool = "0123456789abcdefghijklmnopqrstuvwxyz";
const int base = 36;
memcpy( buf, PEERID_PREFIX, 8 );
tr_cryptoRandBuf( buf+8, 11 );
for( i=8; i<19; ++i ) {
val = buf[i] % base;
total += val;
buf[i] = pool[val];
}
val = total % base ? base - ( total % base ) : 0;
buf[19] = pool[val];
buf[20] = '\0';
return buf;
}
const uint8_t*
tr_getPeerId( void )
{
static uint8_t * id = NULL;
if( id == NULL )
id = tr_peerIdNew( );
return id;
}
/***
****
***/
tr_encryption_mode
tr_sessionGetEncryption( tr_session * session )
{
assert( session );
return session->encryptionMode;
}
void
tr_sessionSetEncryption( tr_session * session,
tr_encryption_mode mode )
{
assert( session );
assert( mode == TR_ENCRYPTION_PREFERRED
|| mode == TR_ENCRYPTION_REQUIRED
|| mode == TR_CLEAR_PREFERRED );
session->encryptionMode = mode;
}
/***
****
***/
struct tr_bindinfo
{
int socket;
tr_address addr;
struct event * ev;
};
static void
close_bindinfo( struct tr_bindinfo * b )
{
if( ( b != NULL ) && ( b->socket >=0 ) )
{
event_free( b->ev );
b->ev = NULL;
tr_netCloseSocket( b->socket );
}
}
static void
close_incoming_peer_port( tr_session * session )
{
close_bindinfo( session->public_ipv4 );
close_bindinfo( session->public_ipv6 );
}
static void
free_incoming_peer_port( tr_session * session )
{
close_bindinfo( session->public_ipv4 );
tr_free( session->public_ipv4 );
session->public_ipv4 = NULL;
close_bindinfo( session->public_ipv6 );
tr_free( session->public_ipv6 );
session->public_ipv6 = NULL;
}
static void
accept_incoming_peer( int fd, short what UNUSED, void * vsession )
{
int clientSocket;
tr_port clientPort;
tr_address clientAddr;
tr_session * session = vsession;
clientSocket = tr_netAccept( session, fd, &clientAddr, &clientPort );
if( clientSocket > 0 ) {
tr_deepLog( __FILE__, __LINE__, NULL, "new incoming connection %d (%s)",
clientSocket, tr_peerIoAddrStr( &clientAddr, clientPort ) );
tr_peerMgrAddIncoming( session->peerMgr, &clientAddr, clientPort, clientSocket );
}
}
static void
open_incoming_peer_port( tr_session * session )
{
struct tr_bindinfo * b;
/* bind an ipv4 port to listen for incoming peers... */
b = session->public_ipv4;
b->socket = tr_netBindTCP( &b->addr, session->private_peer_port, FALSE );
if( b->socket >= 0 ) {
b->ev = event_new( session->event_base, b->socket, EV_READ | EV_PERSIST, accept_incoming_peer, session );
event_add( b->ev, NULL );
}
/* and do the exact same thing for ipv6, if it's supported... */
if( tr_net_hasIPv6( session->private_peer_port ) ) {
b = session->public_ipv6;
b->socket = tr_netBindTCP( &b->addr, session->private_peer_port, FALSE );
if( b->socket >= 0 ) {
b->ev = event_new( session->event_base, b->socket, EV_READ | EV_PERSIST, accept_incoming_peer, session );
event_add( b->ev, NULL );
}
}
}
const tr_address*
tr_sessionGetPublicAddress( const tr_session * session, int tr_af_type, tr_bool * is_default_value )
{
const char * default_value;
const struct tr_bindinfo * bindinfo;
switch( tr_af_type )
{
case TR_AF_INET:
bindinfo = session->public_ipv4;
default_value = TR_DEFAULT_BIND_ADDRESS_IPV4;
break;
case TR_AF_INET6:
bindinfo = session->public_ipv6;
default_value = TR_DEFAULT_BIND_ADDRESS_IPV6;
break;
default:
bindinfo = NULL;
default_value = "";
break;
}
if( is_default_value && bindinfo )
*is_default_value = !tr_strcmp0( default_value, tr_ntop_non_ts( &bindinfo->addr ) );
return bindinfo ? &bindinfo->addr : NULL;
}
/***
****
***/
#ifdef TR_EMBEDDED
#define TR_DEFAULT_ENCRYPTION TR_CLEAR_PREFERRED
#else
#define TR_DEFAULT_ENCRYPTION TR_ENCRYPTION_PREFERRED
#endif
static int
parse_tos(const char *string)
{
char *p;
int value;
if(strcasecmp(string, "") == 0 || strcasecmp(string, "default") == 0)
return 0;
else if(strcasecmp(string, "lowcost") == 0 ||
strcasecmp(string, "mincost") == 0)
return 0x10;
else if(strcasecmp(string, "throughput") == 0)
return 0x08;
else if(strcasecmp(string, "reliability") == 0)
return 0x04;
else if(strcasecmp(string, "lowdelay") == 0)
return 0x02;
value = strtol(string, &p, 0);
if(p == NULL || p == string)
return 0;
return value;
}
static const char *
format_tos(int value)
{
static char buf[8];
switch(value) {
case 0: return "default";
case 0x10: return "lowcost";
case 0x08: return "throughput";
case 0x04: return "reliability";
case 0x02: return "lowdelay";
default:
snprintf(buf, 8, "%d", value);
return buf;
}
}
void
tr_sessionGetDefaultSettings( const char * configDir UNUSED, tr_benc * d )
{
assert( tr_bencIsDict( d ) );
tr_bencDictReserve( d, 60 );
tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
tr_bencDictAddStr ( d, TR_PREFS_KEY_BLOCKLIST_URL, "http://www.example.com/blocklist" );
tr_bencDictAddInt ( d, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, DEFAULT_CACHE_SIZE_MB );
tr_bencDictAddBool( d, TR_PREFS_KEY_DHT_ENABLED, TRUE );
tr_bencDictAddBool( d, TR_PREFS_KEY_LPD_ENABLED, FALSE );
tr_bencDictAddStr ( d, TR_PREFS_KEY_DOWNLOAD_DIR, tr_getDefaultDownloadDir( ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED_KBps, 100 );
tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ENCRYPTION, TR_DEFAULT_ENCRYPTION );
tr_bencDictAddInt ( d, TR_PREFS_KEY_IDLE_LIMIT, 30 );
tr_bencDictAddBool( d, TR_PREFS_KEY_IDLE_LIMIT_ENABLED, FALSE );
tr_bencDictAddStr ( d, TR_PREFS_KEY_INCOMPLETE_DIR, tr_getDefaultDownloadDir( ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, FALSE );
tr_bencDictAddBool( d, TR_PREFS_KEY_LAZY_BITFIELD, TRUE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_MSGLEVEL, TR_MSG_INF );
tr_bencDictAddInt ( d, TR_PREFS_KEY_OPEN_FILE_LIMIT, atoi( TR_DEFAULT_OPEN_FILE_LIMIT_STR ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi( TR_DEFAULT_PEER_LIMIT_GLOBAL_STR ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_LIMIT_TORRENT, atoi( TR_DEFAULT_PEER_LIMIT_TORRENT_STR ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT, atoi( TR_DEFAULT_PEER_PORT_STR ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, FALSE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT_RANDOM_LOW, 49152 );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT_RANDOM_HIGH, 65535 );
tr_bencDictAddStr ( d, TR_PREFS_KEY_PEER_SOCKET_TOS, TR_DEFAULT_PEER_SOCKET_TOS_STR );
tr_bencDictAddBool( d, TR_PREFS_KEY_PEX_ENABLED, TRUE );
tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PREALLOCATION, TR_PREALLOCATE_SPARSE );
tr_bencDictAddReal( d, TR_PREFS_KEY_RATIO, 2.0 );
tr_bencDictAddBool( d, TR_PREFS_KEY_RATIO_ENABLED, FALSE );
tr_bencDictAddBool( d, TR_PREFS_KEY_RENAME_PARTIAL_FILES, TRUE );
tr_bencDictAddBool( d, TR_PREFS_KEY_RPC_AUTH_REQUIRED, FALSE );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_BIND_ADDRESS, "0.0.0.0" );
tr_bencDictAddBool( d, TR_PREFS_KEY_RPC_ENABLED, FALSE );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_PASSWORD, "" );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_USERNAME, "" );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_WHITELIST, TR_DEFAULT_RPC_WHITELIST );
tr_bencDictAddBool( d, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, TRUE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_RPC_PORT, atoi( TR_DEFAULT_RPC_PORT_STR ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_URL, TR_DEFAULT_RPC_URL_STR );
tr_bencDictAddStr ( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, "" );
tr_bencDictAddBool( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, FALSE );
tr_bencDictAddBool( d, TR_PREFS_KEY_ALT_SPEED_ENABLED, FALSE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_UP_KBps, 50 ); /* half the regular */
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, 50 ); /* half the regular */
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, 540 ); /* 9am */
tr_bencDictAddBool( d, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, FALSE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_TIME_END, 1020 ); /* 5pm */
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, TR_SCHED_ALL );
tr_bencDictAddInt ( d, TR_PREFS_KEY_USPEED_KBps, 100 );
tr_bencDictAddBool( d, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
tr_bencDictAddInt ( d, TR_PREFS_KEY_UMASK, 022 );
tr_bencDictAddInt ( d, TR_PREFS_KEY_UPLOAD_SLOTS_PER_TORRENT, 14 );
tr_bencDictAddStr ( d, TR_PREFS_KEY_BIND_ADDRESS_IPV4, TR_DEFAULT_BIND_ADDRESS_IPV4 );
tr_bencDictAddStr ( d, TR_PREFS_KEY_BIND_ADDRESS_IPV6, TR_DEFAULT_BIND_ADDRESS_IPV6 );
tr_bencDictAddBool( d, TR_PREFS_KEY_START, TRUE );
tr_bencDictAddBool( d, TR_PREFS_KEY_TRASH_ORIGINAL, FALSE );
}
void
tr_sessionGetSettings( tr_session * s, struct tr_benc * d )
{
assert( tr_bencIsDict( d ) );
tr_bencDictReserve( d, 60 );
tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, tr_blocklistIsEnabled( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_BLOCKLIST_URL, tr_blocklistGetURL( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, tr_sessionGetCacheLimit_MB( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_DHT_ENABLED, s->isDHTEnabled );
tr_bencDictAddBool( d, TR_PREFS_KEY_LPD_ENABLED, s->isLPDEnabled );
tr_bencDictAddStr ( d, TR_PREFS_KEY_DOWNLOAD_DIR, s->downloadDir );
tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED_KBps, tr_sessionGetSpeedLimit_KBps( s, TR_DOWN ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, tr_sessionIsSpeedLimited( s, TR_DOWN ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ENCRYPTION, s->encryptionMode );
tr_bencDictAddInt ( d, TR_PREFS_KEY_IDLE_LIMIT, tr_sessionGetIdleLimit( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_IDLE_LIMIT_ENABLED, tr_sessionIsIdleLimited( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_INCOMPLETE_DIR, tr_sessionGetIncompleteDir( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, tr_sessionIsIncompleteDirEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_LAZY_BITFIELD, s->useLazyBitfield );
tr_bencDictAddInt ( d, TR_PREFS_KEY_MSGLEVEL, tr_getMessageLevel( ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_OPEN_FILE_LIMIT, tr_fdGetFileLimit( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, tr_sessionGetPeerLimit( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_LIMIT_TORRENT, s->peerLimitPerTorrent );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT, tr_sessionGetPeerPort( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, s->isPortRandom );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT_RANDOM_LOW, s->randomPortLow );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PEER_PORT_RANDOM_HIGH, s->randomPortHigh );
tr_bencDictAddStr ( d, TR_PREFS_KEY_PEER_SOCKET_TOS, format_tos(s->peerSocketTOS) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_PEER_CONGESTION_ALGORITHM, s->peer_congestion_algorithm );
tr_bencDictAddBool( d, TR_PREFS_KEY_PEX_ENABLED, s->isPexEnabled );
tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, tr_sessionIsPortForwardingEnabled( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_PREALLOCATION, s->preallocationMode );
tr_bencDictAddReal( d, TR_PREFS_KEY_RATIO, s->desiredRatio );
tr_bencDictAddBool( d, TR_PREFS_KEY_RATIO_ENABLED, s->isRatioLimited );
tr_bencDictAddBool( d, TR_PREFS_KEY_RENAME_PARTIAL_FILES, tr_sessionIsIncompleteFileNamingEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_RPC_AUTH_REQUIRED, tr_sessionIsRPCPasswordEnabled( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_BIND_ADDRESS, tr_sessionGetRPCBindAddress( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_RPC_ENABLED, tr_sessionIsRPCEnabled( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_PASSWORD, tr_sessionGetRPCPassword( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_RPC_PORT, tr_sessionGetRPCPort( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_URL, tr_sessionGetRPCUrl( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_USERNAME, tr_sessionGetRPCUsername( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_RPC_WHITELIST, tr_sessionGetRPCWhitelist( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, tr_sessionGetRPCWhitelistEnabled( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, tr_sessionIsTorrentDoneScriptEnabled( s ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, tr_sessionGetTorrentDoneScript( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_ALT_SPEED_ENABLED, tr_sessionUsesAltSpeed( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_UP_KBps, tr_sessionGetAltSpeed_KBps( s, TR_UP ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, tr_sessionGetAltSpeed_KBps( s, TR_DOWN ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, tr_sessionGetAltSpeedBegin( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, tr_sessionUsesAltSpeedTime( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_TIME_END, tr_sessionGetAltSpeedEnd( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, tr_sessionGetAltSpeedDay( s ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_USPEED_KBps, tr_sessionGetSpeedLimit_KBps( s, TR_UP ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_USPEED_ENABLED, tr_sessionIsSpeedLimited( s, TR_UP ) );
tr_bencDictAddInt ( d, TR_PREFS_KEY_UMASK, s->umask );
tr_bencDictAddInt ( d, TR_PREFS_KEY_UPLOAD_SLOTS_PER_TORRENT, s->uploadSlotsPerTorrent );
tr_bencDictAddStr ( d, TR_PREFS_KEY_BIND_ADDRESS_IPV4, tr_ntop_non_ts( &s->public_ipv4->addr ) );
tr_bencDictAddStr ( d, TR_PREFS_KEY_BIND_ADDRESS_IPV6, tr_ntop_non_ts( &s->public_ipv6->addr ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_START, !tr_sessionGetPaused( s ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_TRASH_ORIGINAL, tr_sessionGetDeleteSource( s ) );
}
tr_bool
tr_sessionLoadSettings( tr_benc * d, const char * configDir, const char * appName )
{
int err = 0;
char * filename;
tr_benc fileSettings;
tr_benc sessionDefaults;
tr_benc tmp;
tr_bool success = FALSE;
assert( tr_bencIsDict( d ) );
/* initializing the defaults: caller may have passed in some app-level defaults.
* preserve those and use the session defaults to fill in any missing gaps. */
tr_bencInitDict( &sessionDefaults, 0 );
tr_sessionGetDefaultSettings( configDir, &sessionDefaults );
tr_bencMergeDicts( &sessionDefaults, d );
tmp = *d; *d = sessionDefaults; sessionDefaults = tmp;
/* if caller didn't specify a config dir, use the default */
if( !configDir || !*configDir )
configDir = tr_getDefaultConfigDir( appName );
/* file settings override the defaults */
filename = tr_buildPath( configDir, "settings.json", NULL );
err = tr_bencLoadFile( &fileSettings, TR_FMT_JSON, filename );
if( !err ) {
tr_bencMergeDicts( d, &fileSettings );
tr_bencFree( &fileSettings );
}
/* cleanup */
tr_bencFree( &sessionDefaults );
tr_free( filename );
success = (err==0) || (err==ENOENT);
return success;
}
void
tr_sessionSaveSettings( tr_session * session,
const char * configDir,
const tr_benc * clientSettings )
{
tr_benc settings;
char * filename = tr_buildPath( configDir, "settings.json", NULL );
assert( tr_bencIsDict( clientSettings ) );
tr_bencInitDict( &settings, 0 );
/* the existing file settings are the fallback values */
{
tr_benc fileSettings;
const int err = tr_bencLoadFile( &fileSettings, TR_FMT_JSON, filename );
if( !err )
{
tr_bencMergeDicts( &settings, &fileSettings );
tr_bencFree( &fileSettings );
}
}
/* the client's settings override the file settings */
tr_bencMergeDicts( &settings, clientSettings );
/* the session's true values override the file & client settings */
{
tr_benc sessionSettings;
tr_bencInitDict( &sessionSettings, 0 );
tr_sessionGetSettings( session, &sessionSettings );
tr_bencMergeDicts( &settings, &sessionSettings );
tr_bencFree( &sessionSettings );
}
/* save the result */
tr_bencToFile( &settings, TR_FMT_JSON, filename );
/* cleanup */
tr_free( filename );
tr_bencFree( &settings );
}
/***
****
***/
/**
* Periodically save the .resume files of any torrents whose
* status has recently changed. This prevents loss of metadata
* in the case of a crash, unclean shutdown, clumsy user, etc.
*/
static void
onSaveTimer( int foo UNUSED, short bar UNUSED, void * vsession )
{
tr_torrent * tor = NULL;
tr_session * session = vsession;
if( tr_cacheFlushDone( session->cache ) )
tr_err( "Error while flushing completed pieces from cache" );
while(( tor = tr_torrentNext( session, tor )))
tr_torrentSave( tor );
tr_statsSaveDirty( session );
tr_timerAdd( session->saveTimer, SAVE_INTERVAL_SECS, 0 );
}
/***
****
***/
static void tr_sessionInitImpl( void * );
struct init_data
{
tr_session * session;
const char * configDir;
tr_bool done;
tr_bool messageQueuingEnabled;
tr_benc * clientSettings;
};
tr_session *
tr_sessionInit( const char * tag,
const char * configDir,
tr_bool messageQueuingEnabled,
tr_benc * clientSettings )
{
int64_t i;
tr_session * session;
struct init_data data;
assert( tr_bencIsDict( clientSettings ) );
tr_timeUpdate( time( NULL ) );
/* initialize the bare skeleton of the session object */
session = tr_new0( tr_session, 1 );
session->udp_socket = -1;
session->udp6_socket = -1;
session->bandwidth = tr_bandwidthNew( session, NULL );
session->lock = tr_lockNew( );
session->cache = tr_cacheNew( 1024*1024*2 );
session->tag = tr_strdup( tag );
session->magicNumber = SESSION_MAGIC_NUMBER;
session->buffer = tr_valloc( SESSION_BUFFER_SIZE );
tr_bencInitList( &session->removedTorrents, 0 );
/* nice to start logging at the very beginning */
if( tr_bencDictFindInt( clientSettings, TR_PREFS_KEY_MSGLEVEL, &i ) )
tr_setMessageLevel( i );
/* start the libtransmission thread */
tr_netInit( ); /* must go before tr_eventInit */
tr_eventInit( session );
assert( session->events != NULL );
/* run the rest in the libtransmission thread */
data.done = FALSE;
data.session = session;
data.configDir = configDir;
data.messageQueuingEnabled = messageQueuingEnabled;
data.clientSettings = clientSettings;
tr_runInEventThread( session, tr_sessionInitImpl, &data );
while( !data.done )
tr_wait_msec( 100 );
return session;
}
static void turtleCheckClock( tr_session * s, struct tr_turtle_info * t );
static void
onNowTimer( int foo UNUSED, short bar UNUSED, void * vsession )
{
int usec;
const int min = 100;
const int max = 999999;
struct timeval tv;
tr_torrent * tor = NULL;
tr_session * session = vsession;
assert( tr_isSession( session ) );
assert( session->nowTimer != NULL );
/**
*** tr_session things to do once per second
**/
tr_timeUpdate( time( NULL ) );
if( session->turtle.isClockEnabled )
turtleCheckClock( session, &session->turtle );
while(( tor = tr_torrentNext( session, tor ))) {
if( tor->isRunning ) {
if( tr_torrentIsSeed( tor ) )
++tor->secondsSeeding;
else
++tor->secondsDownloading;
}
}
/**
*** Set the timer
**/
/* schedule the next timer for right after the next second begins */
gettimeofday( &tv, NULL );
usec = 1000000 - tv.tv_usec;
if( usec > max ) usec = max;
if( usec < min ) usec = min;
tr_timerAdd( session->nowTimer, 0, usec );
/* fprintf( stderr, "time %zu sec, %zu microsec\n", (size_t)tr_time(), (size_t)tv.tv_usec ); */
}
static void loadBlocklists( tr_session * session );
static void
tr_sessionInitImpl( void * vdata )
{
tr_benc settings;
struct init_data * data = vdata;
tr_benc * clientSettings = data->clientSettings;
tr_session * session = data->session;
assert( tr_amInEventThread( session ) );
assert( tr_bencIsDict( clientSettings ) );
dbgmsg( "tr_sessionInit: the session's top-level bandwidth object is %p",
session->bandwidth );
tr_bencInitDict( &settings, 0 );
tr_sessionGetDefaultSettings( data->configDir, &settings );
tr_bencMergeDicts( &settings, clientSettings );
assert( session->event_base != NULL );
session->nowTimer = evtimer_new( session->event_base, onNowTimer, session );
onNowTimer( 0, 0, session );
#ifndef WIN32
/* Don't exit when writing on a broken socket */
signal( SIGPIPE, SIG_IGN );
#endif
tr_setMessageQueuing( data->messageQueuingEnabled );
tr_setConfigDir( session, data->configDir );
session->peerMgr = tr_peerMgrNew( session );
session->shared = tr_sharedInit( session );
/**
*** Blocklist
**/
{
char * filename = tr_buildPath( session->configDir, "blocklists", NULL );
tr_mkdirp( filename, 0777 );
tr_free( filename );
loadBlocklists( session );
}
assert( tr_isSession( session ) );
session->saveTimer = evtimer_new( session->event_base, onSaveTimer, session );
tr_timerAdd( session->saveTimer, SAVE_INTERVAL_SECS, 0 );
tr_announcerInit( session );
/* first %s is the application name
second %s is the version number */
tr_inf( _( "%s %s started" ), TR_NAME, LONG_VERSION_STRING );
tr_statsInit( session );
tr_webInit( session );
tr_sessionSet( session, &settings );
tr_udpInit( session, &session->public_ipv4->addr );
if( session->isLPDEnabled )
tr_lpdInit( session, &session->public_ipv4->addr );
/* cleanup */
tr_bencFree( &settings );
data->done = TRUE;
}
static void turtleBootstrap( tr_session *, struct tr_turtle_info * );
static void setPeerPort( tr_session * session, tr_port port );
static void
sessionSetImpl( void * vdata )
{
int64_t i;
double d;
tr_bool boolVal;
const char * str;
struct tr_bindinfo b;
struct init_data * data = vdata;
tr_session * session = data->session;
tr_benc * settings = data->clientSettings;
struct tr_turtle_info * turtle = &session->turtle;
assert( tr_isSession( session ) );
assert( tr_bencIsDict( settings ) );
assert( tr_amInEventThread( session ) );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_MSGLEVEL, &i ) )
tr_setMessageLevel( i );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_UMASK, &i ) ) {
session->umask = (mode_t)i;
umask( session->umask );
}
/* misc features */
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, &i ) )
tr_sessionSetCacheLimit_MB( session, i );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_LAZY_BITFIELD, &boolVal ) )
tr_sessionSetLazyBitfieldEnabled( session, boolVal );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_PEER_LIMIT_TORRENT, &i ) )
tr_sessionSetPeerLimitPerTorrent( session, i );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
tr_sessionSetPexEnabled( session, boolVal );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_DHT_ENABLED, &boolVal ) )
tr_sessionSetDHTEnabled( session, boolVal );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_LPD_ENABLED, &boolVal ) )
tr_sessionSetLPDEnabled( session, boolVal );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ENCRYPTION, &i ) )
tr_sessionSetEncryption( session, i );
if( tr_bencDictFindStr( settings, TR_PREFS_KEY_PEER_SOCKET_TOS, &str ) )
session->peerSocketTOS = parse_tos( str );
if( tr_bencDictFindStr( settings, TR_PREFS_KEY_PEER_CONGESTION_ALGORITHM, &str ) )
session->peer_congestion_algorithm = tr_strdup(str);
else
session->peer_congestion_algorithm = tr_strdup("");
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_BLOCKLIST_ENABLED, &boolVal ) )
tr_blocklistSetEnabled( session, boolVal );
if( tr_bencDictFindStr( settings, TR_PREFS_KEY_BLOCKLIST_URL, &str ) )
tr_blocklistSetURL( session, str );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_START, &boolVal ) )
tr_sessionSetPaused( session, !boolVal );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_TRASH_ORIGINAL, &boolVal) )
tr_sessionSetDeleteSource( session, boolVal );
/* files and directories */
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_PREALLOCATION, &i ) )
session->preallocationMode = i;
if( tr_bencDictFindStr( settings, TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
tr_sessionSetDownloadDir( session, str );
if( tr_bencDictFindStr( settings, TR_PREFS_KEY_INCOMPLETE_DIR, &str ) )
tr_sessionSetIncompleteDir( session, str );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, &boolVal ) )
tr_sessionSetIncompleteDirEnabled( session, boolVal );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_RENAME_PARTIAL_FILES, &boolVal ) )
tr_sessionSetIncompleteFileNamingEnabled( session, boolVal );
/* rpc server */
if( session->rpcServer != NULL ) /* close the old one */
tr_rpcClose( &session->rpcServer );
session->rpcServer = tr_rpcInit( session, settings );
/* public addresses */
free_incoming_peer_port( session );
str = TR_PREFS_KEY_BIND_ADDRESS_IPV4;
tr_bencDictFindStr( settings, TR_PREFS_KEY_BIND_ADDRESS_IPV4, &str );
if( !tr_pton( str, &b.addr ) || ( b.addr.type != TR_AF_INET ) )
b.addr = tr_inaddr_any;
b.socket = -1;
session->public_ipv4 = tr_memdup( &b, sizeof( struct tr_bindinfo ) );
str = TR_PREFS_KEY_BIND_ADDRESS_IPV6;
tr_bencDictFindStr( settings, TR_PREFS_KEY_BIND_ADDRESS_IPV6, &str );
if( !tr_pton( str, &b.addr ) || ( b.addr.type != TR_AF_INET6 ) )
b.addr = tr_in6addr_any;
b.socket = -1;
session->public_ipv6 = tr_memdup( &b, sizeof( struct tr_bindinfo ) );
/* incoming peer port */
if( tr_bencDictFindInt ( settings, TR_PREFS_KEY_PEER_PORT_RANDOM_LOW, &i ) )
session->randomPortLow = i;
if( tr_bencDictFindInt ( settings, TR_PREFS_KEY_PEER_PORT_RANDOM_HIGH, &i ) )
session->randomPortHigh = i;
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, &boolVal ) )
tr_sessionSetPeerPortRandomOnStart( session, boolVal );
if( !tr_bencDictFindInt( settings, TR_PREFS_KEY_PEER_PORT, &i ) )
i = session->private_peer_port;
setPeerPort( session, boolVal ? getRandomPort( session ) : i );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
tr_sessionSetPortForwardingEnabled( session, boolVal );
/* file and peer socket limits */
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &i ) )
tr_fdSetPeerLimit( session, i );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_OPEN_FILE_LIMIT, &i ) )
tr_fdSetFileLimit( session, i );
/**
**/
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_UPLOAD_SLOTS_PER_TORRENT, &i ) )
session->uploadSlotsPerTorrent = i;
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_USPEED_KBps, &i ) )
tr_sessionSetSpeedLimit_KBps( session, TR_UP, i );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_USPEED_ENABLED, &boolVal ) )
tr_sessionLimitSpeed( session, TR_UP, boolVal );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_DSPEED_KBps, &i ) )
tr_sessionSetSpeedLimit_KBps( session, TR_DOWN, i );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_DSPEED_ENABLED, &boolVal ) )
tr_sessionLimitSpeed( session, TR_DOWN, boolVal );
if( tr_bencDictFindReal( settings, TR_PREFS_KEY_RATIO, &d ) )
tr_sessionSetRatioLimit( session, d );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_RATIO_ENABLED, &boolVal ) )
tr_sessionSetRatioLimited( session, boolVal );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_IDLE_LIMIT, &i ) )
tr_sessionSetIdleLimit( session, i );
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_IDLE_LIMIT_ENABLED, &boolVal ) )
tr_sessionSetIdleLimited( session, boolVal );
/**
*** Turtle Mode
**/
/* update the turtle mode's fields */
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ALT_SPEED_UP_KBps, &i ) )
turtle->speedLimit_Bps[TR_UP] = toSpeedBytes( i );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, &i ) )
turtle->speedLimit_Bps[TR_DOWN] = toSpeedBytes( i );
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &i ) )
turtle->beginMinute = i;
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ALT_SPEED_TIME_END, &i ) )
turtle->endMinute = i;
if( tr_bencDictFindInt( settings, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &i ) )
turtle->days = i;
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &boolVal ) )
turtle->isClockEnabled = boolVal;
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_ALT_SPEED_ENABLED, &boolVal ) )
turtle->isEnabled = boolVal;
turtleBootstrap( session, turtle );
/**
*** Scripts
**/
if( tr_bencDictFindBool( settings, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, &boolVal ) )
tr_sessionSetTorrentDoneScriptEnabled( session, boolVal );
if( tr_bencDictFindStr( settings, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, &str ) )
tr_sessionSetTorrentDoneScript( session, str );
data->done = TRUE;
}
void
tr_sessionSet( tr_session * session, struct tr_benc * settings )
{
struct init_data data;
data.done = FALSE;
data.session = session;
data.clientSettings = settings;
/* run the rest in the libtransmission thread */
tr_runInEventThread( session, sessionSetImpl, &data );
while( !data.done )
tr_wait_msec( 100 );
}
/***
****
***/
void
tr_sessionSetDownloadDir( tr_session * session, const char * dir )
{
assert( tr_isSession( session ) );
if( session->downloadDir != dir )
{
tr_free( session->downloadDir );
session->downloadDir = tr_strdup( dir );
}
}
const char *
tr_sessionGetDownloadDir( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->downloadDir;
}
int64_t
tr_sessionGetDownloadDirFreeSpace( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_getFreeSpace( session->downloadDir );
}
/***
****
***/
void
tr_sessionSetIncompleteFileNamingEnabled( tr_session * session, tr_bool b )
{
assert( tr_isSession( session ) );
assert( tr_isBool( b ) );
session->isIncompleteFileNamingEnabled = b;
}
tr_bool
tr_sessionIsIncompleteFileNamingEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isIncompleteFileNamingEnabled;
}
/***
****
***/
void
tr_sessionSetIncompleteDir( tr_session * session, const char * dir )
{
assert( tr_isSession( session ) );
if( session->incompleteDir != dir )
{
tr_free( session->incompleteDir );
session->incompleteDir = tr_strdup( dir );
}
}
const char*
tr_sessionGetIncompleteDir( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->incompleteDir;
}
void
tr_sessionSetIncompleteDirEnabled( tr_session * session, tr_bool b )
{
assert( tr_isSession( session ) );
assert( tr_isBool( b ) );
session->isIncompleteDirEnabled = b;
}
tr_bool
tr_sessionIsIncompleteDirEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isIncompleteDirEnabled;
}
/***
****
***/
void*
tr_sessionGetBuffer( tr_session * session )
{
assert( tr_isSession( session ) );
assert( !session->bufferInUse );
assert( tr_amInEventThread( session ) );
session->bufferInUse = TRUE;
return session->buffer;
}
void
tr_sessionReleaseBuffer( tr_session * session )
{
assert( tr_isSession( session ) );
assert( session->bufferInUse );
assert( tr_amInEventThread( session ) );
session->bufferInUse = FALSE;
}
void
tr_sessionLock( tr_session * session )
{
assert( tr_isSession( session ) );
tr_lockLock( session->lock );
}
void
tr_sessionUnlock( tr_session * session )
{
assert( tr_isSession( session ) );
tr_lockUnlock( session->lock );
}
tr_bool
tr_sessionIsLocked( const tr_session * session )
{
return tr_isSession( session ) && tr_lockHave( session->lock );
}
/***********************************************************************
* tr_setBindPort
***********************************************************************
*
**********************************************************************/
static void
peerPortChanged( void * session )
{
tr_torrent * tor = NULL;
assert( tr_isSession( session ) );
close_incoming_peer_port( session );
open_incoming_peer_port( session );
tr_sharedPortChanged( session );
while(( tor = tr_torrentNext( session, tor )))
tr_torrentChangeMyPort( tor );
}
static void
setPeerPort( tr_session * session, tr_port port )
{
session->private_peer_port = port;
session->public_peer_port = port;
tr_runInEventThread( session, peerPortChanged, session );
}
void
tr_sessionSetPeerPort( tr_session * session, tr_port port )
{
if( tr_isSession( session ) && ( session->private_peer_port != port ) )
{
setPeerPort( session, port );
}
}
tr_port
tr_sessionGetPeerPort( const tr_session * session )
{
return tr_isSession( session ) ? session->private_peer_port : 0;
}
tr_port
tr_sessionSetPeerPortRandom( tr_session * session )
{
assert( tr_isSession( session ) );
tr_sessionSetPeerPort( session, getRandomPort( session ) );
return session->private_peer_port;
}
void
tr_sessionSetPeerPortRandomOnStart( tr_session * session,
tr_bool random )
{
assert( tr_isSession( session ) );
session->isPortRandom = random;
}
tr_bool
tr_sessionGetPeerPortRandomOnStart( tr_session * session )
{
assert( tr_isSession( session ) );
return session->isPortRandom;
}
tr_port_forwarding
tr_sessionGetPortForwarding( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_sharedTraversalStatus( session->shared );
}
/***
****
***/
void
tr_sessionSetRatioLimited( tr_session * session, tr_bool isLimited )
{
assert( tr_isSession( session ) );
session->isRatioLimited = isLimited;
}
void
tr_sessionSetRatioLimit( tr_session * session, double desiredRatio )
{
assert( tr_isSession( session ) );
session->desiredRatio = desiredRatio;
}
tr_bool
tr_sessionIsRatioLimited( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isRatioLimited;
}
double
tr_sessionGetRatioLimit( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->desiredRatio;
}
/***
****
***/
void
tr_sessionSetIdleLimited( tr_session * session, tr_bool isLimited )
{
assert( tr_isSession( session ) );
session->isIdleLimited = isLimited;
}
void
tr_sessionSetIdleLimit( tr_session * session, uint16_t idleMinutes )
{
assert( tr_isSession( session ) );
session->idleLimitMinutes = idleMinutes;
}
tr_bool
tr_sessionIsIdleLimited( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isIdleLimited;
}
uint16_t
tr_sessionGetIdleLimit( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->idleLimitMinutes;
}
/***
****
**** SPEED LIMITS
****
***/
tr_bool
tr_sessionGetActiveSpeedLimit_Bps( const tr_session * session, tr_direction dir, int * setme_Bps )
{
int isLimited = TRUE;
if( !tr_isSession( session ) )
return FALSE;
if( tr_sessionUsesAltSpeed( session ) )
*setme_Bps = tr_sessionGetAltSpeed_Bps( session, dir );
else if( tr_sessionIsSpeedLimited( session, dir ) )
*setme_Bps = tr_sessionGetSpeedLimit_Bps( session, dir );
else
isLimited = FALSE;
return isLimited;
}
tr_bool
tr_sessionGetActiveSpeedLimit_KBps( const tr_session * session,
tr_direction dir,
double * setme_KBps )
{
int Bps = 0;
const tr_bool is_active = tr_sessionGetActiveSpeedLimit_Bps( session, dir, &Bps );
*setme_KBps = toSpeedKBps( Bps );
return is_active;
}
static void
updateBandwidth( tr_session * session, tr_direction dir )
{
int limit_Bps = 0;
const tr_bool isLimited = tr_sessionGetActiveSpeedLimit_Bps( session, dir, &limit_Bps );
const tr_bool zeroCase = isLimited && !limit_Bps;
tr_bandwidthSetLimited( session->bandwidth, dir, isLimited && !zeroCase );
tr_bandwidthSetDesiredSpeed_Bps( session->bandwidth, dir, limit_Bps );
}
enum
{
MINUTES_PER_HOUR = 60,
MINUTES_PER_DAY = MINUTES_PER_HOUR * 24,
MINUTES_PER_WEEK = MINUTES_PER_DAY * 7
};
static void
turtleUpdateTable( struct tr_turtle_info * t )
{
int day;
tr_bitfield * b = &t->minutes;
tr_bitfieldClear( b );
for( day=0; day<7; ++day )
{
if( t->days & (1<<day) )
{
int i;
const time_t begin = t->beginMinute;
time_t end = t->endMinute;
if( end <= begin )
end += MINUTES_PER_DAY;
for( i=begin; i<end; ++i )
tr_bitfieldAdd( b, (i+day*MINUTES_PER_DAY) % MINUTES_PER_WEEK );
}
}
}
static void
altSpeedToggled( void * vsession )
{
tr_session * session = vsession;
struct tr_turtle_info * t = &session->turtle;
assert( tr_isSession( session ) );
updateBandwidth( session, TR_UP );
updateBandwidth( session, TR_DOWN );
if( t->callback != NULL )
(*t->callback)( session, t->isEnabled, t->changedByUser, t->callbackUserData );
}
static void
useAltSpeed( tr_session * s, struct tr_turtle_info * t,
tr_bool enabled, tr_bool byUser )
{
assert( tr_isSession( s ) );
assert( t != NULL );
assert( tr_isBool( enabled ) );
assert( tr_isBool( byUser ) );
if( t->isEnabled != enabled )
{
t->isEnabled = enabled;
t->changedByUser = byUser;
tr_runInEventThread( s, altSpeedToggled, s );
}
}
/**
* @param enabled whether turtle should be on/off according to the scheduler
* @param changed whether that's different from the previous minute
*/
static void
testTurtleTime( const struct tr_turtle_info * t,
tr_bool * enabled,
tr_bool * changed )
{
tr_bool e;
struct tm tm;
size_t minute_of_the_week;
const time_t now = tr_time( );
tr_localtime_r( &now, &tm );
minute_of_the_week = tm.tm_wday * MINUTES_PER_DAY
+ tm.tm_hour * MINUTES_PER_HOUR
+ tm.tm_min;
if( minute_of_the_week >= MINUTES_PER_WEEK ) /* leap minutes? */
minute_of_the_week = MINUTES_PER_WEEK - 1;
e = tr_bitfieldHasFast( &t->minutes, minute_of_the_week );
if( enabled != NULL )
*enabled = e;
if( changed != NULL )
{
const size_t prev = minute_of_the_week > 0 ? minute_of_the_week - 1
: MINUTES_PER_WEEK - 1;
*changed = e != tr_bitfieldHasFast( &t->minutes, prev );
}
}
static void
turtleCheckClock( tr_session * s, struct tr_turtle_info * t )
{
tr_bool enabled;
tr_bool changed;
assert( t->isClockEnabled );
testTurtleTime( t, &enabled, &changed );
if( changed )
{
tr_inf( "Time to turn %s turtle mode!", (enabled?"on":"off") );
useAltSpeed( s, t, enabled, FALSE );
}
}
/* Called after the turtle's fields are loaded from an outside source.
* It initializes the implementation fields
* and turns on turtle mode if the clock settings say to. */
static void
turtleBootstrap( tr_session * session, struct tr_turtle_info * turtle )
{
turtle->changedByUser = FALSE;
tr_bitfieldConstruct( &turtle->minutes, MINUTES_PER_WEEK );
turtleUpdateTable( turtle );
if( turtle->isClockEnabled )
testTurtleTime( turtle, &turtle->isEnabled, NULL );
altSpeedToggled( session );
}
/***
**** Primary session speed limits
***/
void
tr_sessionSetSpeedLimit_Bps( tr_session * s, tr_direction d, int Bps )
{
assert( tr_isSession( s ) );
assert( tr_isDirection( d ) );
assert( Bps >= 0 );
s->speedLimit_Bps[d] = Bps;
updateBandwidth( s, d );
}
void
tr_sessionSetSpeedLimit_KBps( tr_session * s, tr_direction d, int KBps )
{
tr_sessionSetSpeedLimit_Bps( s, d, toSpeedBytes( KBps ) );
}
int
tr_sessionGetSpeedLimit_Bps( const tr_session * s, tr_direction d )
{
assert( tr_isSession( s ) );
assert( tr_isDirection( d ) );
return s->speedLimit_Bps[d];
}
int
tr_sessionGetSpeedLimit_KBps( const tr_session * s, tr_direction d )
{
return toSpeedKBps( tr_sessionGetSpeedLimit_Bps( s, d ) );
}
void
tr_sessionLimitSpeed( tr_session * s, tr_direction d, tr_bool b )
{
assert( tr_isSession( s ) );
assert( tr_isDirection( d ) );
assert( tr_isBool( b ) );
s->speedLimitEnabled[d] = b;
updateBandwidth( s, d );
}
tr_bool
tr_sessionIsSpeedLimited( const tr_session * s, tr_direction d )
{
assert( tr_isSession( s ) );
assert( tr_isDirection( d ) );
return s->speedLimitEnabled[d];
}
/***
**** Alternative speed limits that are used during scheduled times
***/
void
tr_sessionSetAltSpeed_Bps( tr_session * s, tr_direction d, int Bps )
{
assert( tr_isSession( s ) );
assert( tr_isDirection( d ) );
assert( Bps >= 0 );
s->turtle.speedLimit_Bps[d] = Bps;
updateBandwidth( s, d );
}
void
tr_sessionSetAltSpeed_KBps( tr_session * s, tr_direction d, int KBps )
{
tr_sessionSetAltSpeed_Bps( s, d, toSpeedBytes( KBps ) );
}
int
tr_sessionGetAltSpeed_Bps( const tr_session * s, tr_direction d )
{
assert( tr_isSession( s ) );
assert( tr_isDirection( d ) );
return s->turtle.speedLimit_Bps[d];
}
int
tr_sessionGetAltSpeed_KBps( const tr_session * s, tr_direction d )
{
return toSpeedKBps( tr_sessionGetAltSpeed_Bps( s, d ) );
}
static void
userPokedTheClock( tr_session * s, struct tr_turtle_info * t )
{
tr_dbg( "Refreshing the turtle mode clock due to user changes" );
turtleUpdateTable( t );
if( t->isClockEnabled )
{
tr_bool enabled, changed;
testTurtleTime( t, &enabled, &changed );
useAltSpeed( s, t, enabled, TRUE );
}
}
void
tr_sessionUseAltSpeedTime( tr_session * s, tr_bool b )
{
struct tr_turtle_info * t = &s->turtle;
assert( tr_isSession( s ) );
assert( tr_isBool ( b ) );
if( t->isClockEnabled != b ) {
t->isClockEnabled = b;
userPokedTheClock( s, t );
}
}
tr_bool
tr_sessionUsesAltSpeedTime( const tr_session * s )
{
assert( tr_isSession( s ) );
return s->turtle.isClockEnabled;
}
void
tr_sessionSetAltSpeedBegin( tr_session * s, int minute )
{
assert( tr_isSession( s ) );
assert( 0<=minute && minute<(60*24) );
if( s->turtle.beginMinute != minute ) {
s->turtle.beginMinute = minute;
userPokedTheClock( s, &s->turtle );
}
}
int
tr_sessionGetAltSpeedBegin( const tr_session * s )
{
assert( tr_isSession( s ) );
return s->turtle.beginMinute;
}
void
tr_sessionSetAltSpeedEnd( tr_session * s, int minute )
{
assert( tr_isSession( s ) );
assert( 0<=minute && minute<(60*24) );
if( s->turtle.endMinute != minute ) {
s->turtle.endMinute = minute;
userPokedTheClock( s, &s->turtle );
}
}
int
tr_sessionGetAltSpeedEnd( const tr_session * s )
{
assert( tr_isSession( s ) );
return s->turtle.endMinute;
}
void
tr_sessionSetAltSpeedDay( tr_session * s, tr_sched_day days )
{
assert( tr_isSession( s ) );
if( s->turtle.days != days ) {
s->turtle.days = days;
userPokedTheClock( s, &s->turtle );
}
}
tr_sched_day
tr_sessionGetAltSpeedDay( const tr_session * s )
{
assert( tr_isSession( s ) );
return s->turtle.days;
}
void
tr_sessionUseAltSpeed( tr_session * session, tr_bool enabled )
{
useAltSpeed( session, &session->turtle, enabled, TRUE );
}
tr_bool
tr_sessionUsesAltSpeed( const tr_session * s )
{
assert( tr_isSession( s ) );
return s->turtle.isEnabled;
}
void
tr_sessionSetAltSpeedFunc( tr_session * session,
tr_altSpeedFunc func,
void * userData )
{
assert( tr_isSession( session ) );
session->turtle.callback = func;
session->turtle.callbackUserData = userData;
}
void
tr_sessionClearAltSpeedFunc( tr_session * session )
{
tr_sessionSetAltSpeedFunc( session, NULL, NULL );
}
/***
****
***/
void
tr_sessionSetPeerLimit( tr_session * session, uint16_t maxGlobalPeers )
{
assert( tr_isSession( session ) );
tr_fdSetPeerLimit( session, maxGlobalPeers );
}
uint16_t
tr_sessionGetPeerLimit( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_fdGetPeerLimit( session );
}
void
tr_sessionSetPeerLimitPerTorrent( tr_session * session, uint16_t n )
{
assert( tr_isSession( session ) );
session->peerLimitPerTorrent = n;
}
uint16_t
tr_sessionGetPeerLimitPerTorrent( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->peerLimitPerTorrent;
}
/***
****
***/
void
tr_sessionSetPaused( tr_session * session, tr_bool isPaused )
{
assert( tr_isSession( session ) );
session->pauseAddedTorrent = isPaused;
}
tr_bool
tr_sessionGetPaused( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->pauseAddedTorrent;
}
void
tr_sessionSetDeleteSource( tr_session * session, tr_bool deleteSource )
{
assert( tr_isSession( session ) );
session->deleteSourceTorrent = deleteSource;
}
tr_bool
tr_sessionGetDeleteSource( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->deleteSourceTorrent;
}
/***
****
***/
int
tr_sessionGetPieceSpeed_Bps( const tr_session * session, tr_direction dir )
{
return tr_isSession( session ) ? tr_bandwidthGetPieceSpeed_Bps( session->bandwidth, 0, dir ) : 0;
}
double
tr_sessionGetPieceSpeed_KBps( const tr_session * session, tr_direction dir )
{
return toSpeedKBps( tr_sessionGetPieceSpeed_Bps( session, dir ) );
}
int
tr_sessionGetRawSpeed_Bps( const tr_session * session, tr_direction dir )
{
return tr_isSession( session ) ? tr_bandwidthGetRawSpeed_Bps( session->bandwidth, 0, dir ) : 0;
}
double
tr_sessionGetRawSpeed_KBps( const tr_session * session, tr_direction dir )
{
return toSpeedKBps( tr_sessionGetRawSpeed_Bps( session, dir ) );
}
int
tr_sessionCountTorrents( const tr_session * session )
{
return tr_isSession( session ) ? session->torrentCount : 0;
}
static int
compareTorrentByCur( const void * va, const void * vb )
{
const tr_torrent * a = *(const tr_torrent**)va;
const tr_torrent * b = *(const tr_torrent**)vb;
const uint64_t aCur = a->downloadedCur + a->uploadedCur;
const uint64_t bCur = b->downloadedCur + b->uploadedCur;
if( aCur != bCur )
return aCur > bCur ? -1 : 1; /* close the biggest torrents first */
return 0;
}
static void closeBlocklists( tr_session * );
static void
sessionCloseImpl( void * vsession )
{
tr_session * session = vsession;
tr_torrent * tor;
int i, n;
tr_torrent ** torrents;
assert( tr_isSession( session ) );
free_incoming_peer_port( session );
if( session->isLPDEnabled )
tr_lpdUninit( session );
tr_udpUninit( session );
event_free( session->saveTimer );
session->saveTimer = NULL;
event_free( session->nowTimer );
session->nowTimer = NULL;
tr_verifyClose( session );
tr_sharedClose( session );
tr_rpcClose( &session->rpcServer );
/* Close the torrents. Get the most active ones first so that
* if we can't get them all closed in a reasonable amount of time,
* at least we get the most important ones first. */
tor = NULL;
n = session->torrentCount;
torrents = tr_new( tr_torrent *, session->torrentCount );
for( i = 0; i < n; ++i )
torrents[i] = tor = tr_torrentNext( session, tor );
qsort( torrents, n, sizeof( tr_torrent* ), compareTorrentByCur );
for( i = 0; i < n; ++i )
tr_torrentFree( torrents[i] );
tr_free( torrents );
tr_cacheFree( session->cache );
session->cache = NULL;
tr_announcerClose( session );
tr_statsClose( session );
tr_peerMgrFree( session->peerMgr );
tr_webClose( session, TR_WEB_CLOSE_WHEN_IDLE );
closeBlocklists( session );
tr_fdClose( session );
session->isClosed = TRUE;
}
static int
deadlineReached( const time_t deadline )
{
return time( NULL ) >= deadline;
}
#define SHUTDOWN_MAX_SECONDS 20
void
tr_sessionClose( tr_session * session )
{
const time_t deadline = time( NULL ) + SHUTDOWN_MAX_SECONDS;
assert( tr_isSession( session ) );
dbgmsg( "shutting down transmission session %p... now is %zu, deadline is %zu", session, (size_t)time(NULL), (size_t)deadline );
/* close the session */
tr_runInEventThread( session, sessionCloseImpl, session );
while( !session->isClosed && !deadlineReached( deadline ) )
{
dbgmsg( "waiting for the libtransmission thread to finish" );
tr_wait_msec( 100 );
}
/* "shared" and "tracker" have live sockets,
* so we need to keep the transmission thread alive
* for a bit while they tell the router & tracker
* that we're closing now */
while( ( session->shared || session->web || session->announcer )
&& !deadlineReached( deadline ) )
{
dbgmsg( "waiting on port unmap (%p) or announcer (%p)... now %zu deadline %zu",
session->shared, session->announcer, (size_t)time(NULL), (size_t)deadline );
tr_wait_msec( 100 );
}
tr_webClose( session, TR_WEB_CLOSE_NOW );
/* close the libtransmission thread */
tr_eventClose( session );
while( session->events != NULL )
{
static tr_bool forced = FALSE;
dbgmsg( "waiting for libtransmission thread to finish... now %zu deadline %zu", (size_t)time(NULL), (size_t)deadline );
tr_wait_msec( 500 );
if( deadlineReached( deadline ) && !forced )
{
dbgmsg( "calling event_loopbreak()" );
forced = TRUE;
event_base_loopbreak( session->event_base );
}
if( deadlineReached( deadline+3 ) )
{
dbgmsg( "deadline+3 reached... calling break...\n" );
break;
}
}
/* free the session memory */
tr_bencFree( &session->removedTorrents );
tr_bandwidthFree( session->bandwidth );
tr_bitfieldDestruct( &session->turtle.minutes );
tr_lockFree( session->lock );
if( session->metainfoLookup ) {
tr_bencFree( session->metainfoLookup );
tr_free( session->metainfoLookup );
}
tr_free( session->torrentDoneScript );
tr_free( session->buffer );
tr_free( session->tag );
tr_free( session->configDir );
tr_free( session->resumeDir );
tr_free( session->torrentDir );
tr_free( session->downloadDir );
tr_free( session->incompleteDir );
tr_free( session->blocklist_url );
tr_free( session->peer_congestion_algorithm );
tr_free( session );
}
tr_torrent **
tr_sessionLoadTorrents( tr_session * session,
tr_ctor * ctor,
int * setmeCount )
{
int i, n = 0;
struct stat sb;
DIR * odir = NULL;
const char * dirname = tr_getTorrentDir( session );
tr_torrent ** torrents;
tr_list * l = NULL, *list = NULL;
assert( tr_isSession( session ) );
tr_ctorSetSave( ctor, FALSE ); /* since we already have them */
if( !stat( dirname, &sb )
&& S_ISDIR( sb.st_mode )
&& ( ( odir = opendir ( dirname ) ) ) )
{
struct dirent *d;
for( d = readdir( odir ); d != NULL; d = readdir( odir ) )
{
if( tr_str_has_suffix( d->d_name, ".torrent" ) )
{
tr_torrent * tor;
char * path = tr_buildPath( dirname, d->d_name, NULL );
tr_ctorSetMetainfoFromFile( ctor, path );
if(( tor = tr_torrentNew( ctor, NULL )))
{
tr_list_append( &list, tor );
++n;
}
tr_free( path );
}
}
closedir( odir );
}
torrents = tr_new( tr_torrent *, n );
for( i = 0, l = list; l != NULL; l = l->next )
torrents[i++] = (tr_torrent*) l->data;
assert( i == n );
tr_list_free( &list, NULL );
if( n )
tr_inf( _( "Loaded %d torrents" ), n );
if( setmeCount )
*setmeCount = n;
return torrents;
}
/***
****
***/
void
tr_sessionSetPexEnabled( tr_session * session,
tr_bool enabled )
{
assert( tr_isSession( session ) );
session->isPexEnabled = enabled != 0;
}
tr_bool
tr_sessionIsPexEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isPexEnabled;
}
tr_bool
tr_sessionAllowsDHT( const tr_session * session UNUSED )
{
return tr_sessionIsDHTEnabled( session );
}
tr_bool
tr_sessionIsDHTEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isDHTEnabled;
}
static void
toggleDHTImpl( void * data )
{
tr_session * session = data;
assert( tr_isSession( session ) );
tr_udpUninit( session );
session->isDHTEnabled = !session->isDHTEnabled;
tr_udpInit( session, &session->public_ipv4->addr );
}
void
tr_sessionSetDHTEnabled( tr_session * session, tr_bool enabled )
{
assert( tr_isSession( session ) );
assert( tr_isBool( enabled ) );
if( ( enabled != 0 ) != ( session->isDHTEnabled != 0 ) )
tr_runInEventThread( session, toggleDHTImpl, session );
}
static void
toggleLPDImpl( void * data )
{
tr_session * session = data;
assert( tr_isSession( session ) );
if( session->isLPDEnabled )
tr_lpdUninit( session );
session->isLPDEnabled = !session->isLPDEnabled;
if( session->isLPDEnabled )
tr_lpdInit( session, &session->public_ipv4->addr );
}
void
tr_sessionSetLPDEnabled( tr_session * session, tr_bool enabled )
{
assert( tr_isSession( session ) );
assert( tr_isBool( enabled ) );
if( ( enabled != 0 ) != ( session->isLPDEnabled != 0 ) )
tr_runInEventThread( session, toggleLPDImpl, session );
}
tr_bool
tr_sessionIsLPDEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isLPDEnabled;
}
tr_bool
tr_sessionAllowsLPD( const tr_session * session )
{
return tr_sessionIsLPDEnabled( session );
}
/***
****
***/
void
tr_sessionSetCacheLimit_MB( tr_session * session, int max_bytes )
{
assert( tr_isSession( session ) );
tr_cacheSetLimit( session->cache, toMemBytes( max_bytes ) );
}
int
tr_sessionGetCacheLimit_MB( const tr_session * session )
{
assert( tr_isSession( session ) );
return toMemMB( tr_cacheGetLimit( session->cache ) );
}
/***
****
***/
void
tr_sessionSetLazyBitfieldEnabled( tr_session * session,
tr_bool enabled )
{
assert( tr_isSession( session ) );
session->useLazyBitfield = enabled != 0;
}
tr_bool
tr_sessionIsLazyBitfieldEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->useLazyBitfield;
}
/***
****
***/
struct port_forwarding_data
{
tr_bool enabled;
struct tr_shared * shared;
};
static void
setPortForwardingEnabled( void * vdata )
{
struct port_forwarding_data * data = vdata;
tr_sharedTraversalEnable( data->shared, data->enabled );
tr_free( data );
}
void
tr_sessionSetPortForwardingEnabled( tr_session * session, tr_bool enabled )
{
struct port_forwarding_data * d;
d = tr_new0( struct port_forwarding_data, 1 );
d->shared = session->shared;
d->enabled = enabled;
tr_runInEventThread( session, setPortForwardingEnabled, d );
}
tr_bool
tr_sessionIsPortForwardingEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_sharedTraversalIsEnabled( session->shared );
}
/***
****
***/
static int
tr_stringEndsWith( const char * str, const char * end )
{
const size_t slen = strlen( str );
const size_t elen = strlen( end );
return slen >= elen && !memcmp( &str[slen - elen], end, elen );
}
static void
loadBlocklists( tr_session * session )
{
int binCount = 0;
int newCount = 0;
struct stat sb;
char * dirname;
DIR * odir = NULL;
tr_list * list = NULL;
const tr_bool isEnabled = session->isBlocklistEnabled;
/* walk through the directory and find blocklists */
dirname = tr_buildPath( session->configDir, "blocklists", NULL );
if( !stat( dirname,
&sb ) && S_ISDIR( sb.st_mode )
&& ( ( odir = opendir( dirname ) ) ) )
{
struct dirent *d;
for( d = readdir( odir ); d; d = readdir( odir ) )
{
char * filename;
if( !d->d_name || d->d_name[0] == '.' ) /* skip dotfiles, ., and ..
*/
continue;
filename = tr_buildPath( dirname, d->d_name, NULL );
if( tr_stringEndsWith( filename, ".bin" ) )
{
/* if we don't already have this blocklist, add it */
if( !tr_list_find( list, filename,
(TrListCompareFunc)strcmp ) )
{
tr_list_append( &list,
_tr_blocklistNew( filename, isEnabled ) );
++binCount;
}
}
else
{
/* strip out the file suffix, if there is one, and add ".bin"
instead */
tr_blocklist * b;
const char * dot = strrchr( d->d_name, '.' );
const int len = dot ? dot - d->d_name
: (int)strlen( d->d_name );
char * tmp = tr_strdup_printf(
"%s" TR_PATH_DELIMITER_STR "%*.*s.bin",
dirname, len, len, d->d_name );
b = _tr_blocklistNew( tmp, isEnabled );
_tr_blocklistSetContent( b, filename );
tr_list_append( &list, b );
++newCount;
tr_free( tmp );
}
tr_free( filename );
}
closedir( odir );
}
session->blocklists = list;
if( binCount )
tr_dbg( "Found %d blocklists in \"%s\"", binCount, dirname );
if( newCount )
tr_dbg( "Found %d new blocklists in \"%s\"", newCount, dirname );
tr_free( dirname );
}
static void
closeBlocklists( tr_session * session )
{
tr_list_free( &session->blocklists,
(TrListForeachFunc)_tr_blocklistFree );
}
void
tr_sessionReloadBlocklists( tr_session * session )
{
closeBlocklists( session );
loadBlocklists( session );
tr_peerMgrOnBlocklistChanged( session->peerMgr );
}
int
tr_blocklistGetRuleCount( const tr_session * session )
{
int n = 0;
tr_list * l;
assert( tr_isSession( session ) );
for( l = session->blocklists; l; l = l->next )
n += _tr_blocklistGetRuleCount( l->data );
return n;
}
tr_bool
tr_blocklistIsEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isBlocklistEnabled;
}
void
tr_blocklistSetEnabled( tr_session * session,
tr_bool isEnabled )
{
tr_list * l;
assert( tr_isSession( session ) );
session->isBlocklistEnabled = isEnabled != 0;
for( l=session->blocklists; l!=NULL; l=l->next )
_tr_blocklistSetEnabled( l->data, isEnabled );
}
tr_bool
tr_blocklistExists( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->blocklists != NULL;
}
int
tr_blocklistSetContent( tr_session * session, const char * contentFilename )
{
tr_list * l;
int ruleCount;
tr_blocklist * b;
const char * defaultName = DEFAULT_BLOCKLIST_FILENAME;
tr_sessionLock( session );
for( b = NULL, l = session->blocklists; !b && l; l = l->next )
if( tr_stringEndsWith( _tr_blocklistGetFilename( l->data ),
defaultName ) )
b = l->data;
if( !b )
{
char * path = tr_buildPath( session->configDir, "blocklists", defaultName, NULL );
b = _tr_blocklistNew( path, session->isBlocklistEnabled );
tr_list_append( &session->blocklists, b );
tr_free( path );
}
ruleCount = _tr_blocklistSetContent( b, contentFilename );
tr_sessionUnlock( session );
return ruleCount;
}
tr_bool
tr_sessionIsAddressBlocked( const tr_session * session,
const tr_address * addr )
{
tr_list * l;
assert( tr_isSession( session ) );
for( l = session->blocklists; l; l = l->next )
if( _tr_blocklistHasAddress( l->data, addr ) )
return TRUE;
return FALSE;
}
void
tr_blocklistSetURL( tr_session * session, const char * url )
{
if( session->blocklist_url != url )
{
tr_free( session->blocklist_url );
session->blocklist_url = tr_strdup( url );
}
}
const char *
tr_blocklistGetURL ( const tr_session * session )
{
return session->blocklist_url;
}
/***
****
***/
static void
metainfoLookupInit( tr_session * session )
{
struct stat sb;
const char * dirname = tr_getTorrentDir( session );
DIR * odir = NULL;
tr_ctor * ctor = NULL;
tr_benc * lookup;
int n = 0;
assert( tr_isSession( session ) );
/* walk through the directory and find the mappings */
lookup = tr_new0( tr_benc, 1 );
tr_bencInitDict( lookup, 0 );
ctor = tr_ctorNew( session );
tr_ctorSetSave( ctor, FALSE ); /* since we already have them */
if( !stat( dirname, &sb ) && S_ISDIR( sb.st_mode ) && ( ( odir = opendir( dirname ) ) ) )
{
struct dirent *d;
while(( d = readdir( odir )))
{
if( tr_str_has_suffix( d->d_name, ".torrent" ) )
{
tr_info inf;
char * path = tr_buildPath( dirname, d->d_name, NULL );
tr_ctorSetMetainfoFromFile( ctor, path );
if( !tr_torrentParse( ctor, &inf ) )
{
++n;
tr_bencDictAddStr( lookup, inf.hashString, path );
}
tr_free( path );
}
}
closedir( odir );
}
tr_ctorFree( ctor );
session->metainfoLookup = lookup;
tr_dbg( "Found %d torrents in \"%s\"", n, dirname );
}
const char*
tr_sessionFindTorrentFile( const tr_session * session,
const char * hashString )
{
const char * filename = NULL;
if( !session->metainfoLookup )
metainfoLookupInit( (tr_session*)session );
tr_bencDictFindStr( session->metainfoLookup, hashString, &filename );
return filename;
}
void
tr_sessionSetTorrentFile( tr_session * session,
const char * hashString,
const char * filename )
{
/* since we walk session->configDir/torrents/ to build the lookup table,
* and tr_sessionSetTorrentFile() is just to tell us there's a new file
* in that same directory, we don't need to do anything here if the
* lookup table hasn't been built yet */
if( session->metainfoLookup )
tr_bencDictAddStr( session->metainfoLookup, hashString, filename );
}
tr_torrent*
tr_torrentNext( tr_session * session,
tr_torrent * tor )
{
tr_torrent * ret;
assert( !session || tr_isSession( session ) );
if( !session )
ret = NULL;
else if( !tor )
ret = session->torrentList;
else
ret = tor->next;
return ret;
}
/***
****
***/
void
tr_sessionSetRPCEnabled( tr_session * session,
tr_bool isEnabled )
{
assert( tr_isSession( session ) );
tr_rpcSetEnabled( session->rpcServer, isEnabled );
}
tr_bool
tr_sessionIsRPCEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcIsEnabled( session->rpcServer );
}
void
tr_sessionSetRPCPort( tr_session * session,
tr_port port )
{
assert( tr_isSession( session ) );
tr_rpcSetPort( session->rpcServer, port );
}
tr_port
tr_sessionGetRPCPort( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetPort( session->rpcServer );
}
void
tr_sessionSetRPCUrl( tr_session * session,
const char * url )
{
assert( tr_isSession( session ) );
tr_rpcSetUrl( session->rpcServer, url );
}
const char*
tr_sessionGetRPCUrl( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetUrl( session->rpcServer );
}
void
tr_sessionSetRPCCallback( tr_session * session,
tr_rpc_func func,
void * user_data )
{
assert( tr_isSession( session ) );
session->rpc_func = func;
session->rpc_func_user_data = user_data;
}
void
tr_sessionSetRPCWhitelist( tr_session * session,
const char * whitelist )
{
assert( tr_isSession( session ) );
tr_rpcSetWhitelist( session->rpcServer, whitelist );
}
const char*
tr_sessionGetRPCWhitelist( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetWhitelist( session->rpcServer );
}
void
tr_sessionSetRPCWhitelistEnabled( tr_session * session,
tr_bool isEnabled )
{
assert( tr_isSession( session ) );
tr_rpcSetWhitelistEnabled( session->rpcServer, isEnabled );
}
tr_bool
tr_sessionGetRPCWhitelistEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetWhitelistEnabled( session->rpcServer );
}
void
tr_sessionSetRPCPassword( tr_session * session,
const char * password )
{
assert( tr_isSession( session ) );
tr_rpcSetPassword( session->rpcServer, password );
}
const char*
tr_sessionGetRPCPassword( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetPassword( session->rpcServer );
}
void
tr_sessionSetRPCUsername( tr_session * session,
const char * username )
{
assert( tr_isSession( session ) );
tr_rpcSetUsername( session->rpcServer, username );
}
const char*
tr_sessionGetRPCUsername( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetUsername( session->rpcServer );
}
void
tr_sessionSetRPCPasswordEnabled( tr_session * session,
tr_bool isEnabled )
{
assert( tr_isSession( session ) );
tr_rpcSetPasswordEnabled( session->rpcServer, isEnabled );
}
tr_bool
tr_sessionIsRPCPasswordEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcIsPasswordEnabled( session->rpcServer );
}
const char *
tr_sessionGetRPCBindAddress( const tr_session * session )
{
assert( tr_isSession( session ) );
return tr_rpcGetBindAddress( session->rpcServer );
}
/****
*****
****/
tr_bool
tr_sessionIsTorrentDoneScriptEnabled( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->isTorrentDoneScriptEnabled;
}
void
tr_sessionSetTorrentDoneScriptEnabled( tr_session * session, tr_bool isEnabled )
{
assert( tr_isSession( session ) );
assert( tr_isBool( isEnabled ) );
session->isTorrentDoneScriptEnabled = isEnabled;
}
const char *
tr_sessionGetTorrentDoneScript( const tr_session * session )
{
assert( tr_isSession( session ) );
return session->torrentDoneScript;
}
void
tr_sessionSetTorrentDoneScript( tr_session * session, const char * scriptFilename )
{
assert( tr_isSession( session ) );
if( session->torrentDoneScript != scriptFilename )
{
tr_free( session->torrentDoneScript );
session->torrentDoneScript = tr_strdup( scriptFilename );
}
}
/***
****
***/
void
tr_sessionSetWebConfigFunc( tr_session * session, void (*func)(tr_session*, void*, const char* ) )
{
session->curl_easy_config_func = func;
}