(trunk) #1538 "Make Web UI URL configurable" -- added to trunk. Patch by wereHamster

This commit is contained in:
Charles Kerr 2010-12-12 18:22:11 +00:00
parent 4ce7896402
commit 3a5d17cfde
7 changed files with 136 additions and 61 deletions

View File

@ -40,6 +40,7 @@
#define MY_NAME "transmission-remote"
#define DEFAULT_HOST "localhost"
#define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR)
#define DEFAULT_URL TR_DEFAULT_RPC_URL_STR "rpc/"
#define ARGUMENTS "arguments"
@ -207,6 +208,8 @@ getUsage( void )
MY_NAME " [port] [options]\n"
" "
MY_NAME " [host:port] [options]\n"
" "
MY_NAME " [http://host:port/transmission/] [options]\n"
"\n"
"See the man page for detailed explanations and many examples.";
}
@ -1591,7 +1594,7 @@ printSessionStats( tr_benc * top )
static char id[4096];
static int
processResponse( const char * host, int port, const void * response, size_t len )
processResponse( const char * rpcurl, const void * response, size_t len )
{
tr_benc top;
int status = EXIT_SUCCESS;
@ -1654,7 +1657,7 @@ processResponse( const char * host, int port, const void * response, size_t len
if( !tr_bencDictFindStr( &top, "result", &str ) )
status |= EXIT_FAILURE;
else {
printf( "%s:%d responded: \"%s\"\n", host, port, str );
printf( "%s responded: \"%s\"\n", rpcurl, str );
if( strcmp( str, "success") )
status |= EXIT_FAILURE;
}
@ -1693,17 +1696,16 @@ tr_curl_easy_init( struct evbuffer * writebuf )
}
static int
flush( const char * host, int port, tr_benc ** benc )
flush( const char * rpcurl, tr_benc ** benc )
{
CURLcode res;
CURL * curl;
int status = EXIT_SUCCESS;
struct evbuffer * buf = evbuffer_new( );
char * json = tr_bencToStr( *benc, TR_FMT_JSON_LEAN, NULL );
char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
curl = tr_curl_easy_init( buf );
curl_easy_setopt( curl, CURLOPT_URL, url );
curl_easy_setopt( curl, CURLOPT_URL, rpcurl );
curl_easy_setopt( curl, CURLOPT_POSTFIELDS, json );
curl_easy_setopt( curl, CURLOPT_TIMEOUT, getTimeoutSecs( json ) );
@ -1712,7 +1714,7 @@ flush( const char * host, int port, tr_benc ** benc )
if(( res = curl_easy_perform( curl )))
{
tr_nerr( MY_NAME, "(%s) %s", url, curl_easy_strerror( res ) );
tr_nerr( MY_NAME, "(%s) %s", rpcurl, curl_easy_strerror( res ) );
status |= EXIT_FAILURE;
}
else
@ -1721,7 +1723,7 @@ flush( const char * host, int port, tr_benc ** benc )
curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
switch( response ) {
case 200:
status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
status |= processResponse( rpcurl, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
break;
case 409:
/* session id failed. our curl header func has already
@ -1729,7 +1731,7 @@ flush( const char * host, int port, tr_benc ** benc )
* build a new CURL* and try again */
curl_easy_cleanup( curl );
curl = NULL;
flush( host, port, benc );
flush( rpcurl, benc );
benc = NULL;
break;
default:
@ -1740,7 +1742,6 @@ flush( const char * host, int port, tr_benc ** benc )
}
/* cleanup */
tr_free( url );
tr_free( json );
evbuffer_free( buf );
if( curl != 0 )
@ -1787,7 +1788,7 @@ ensure_tset( tr_benc ** tset )
}
static int
processArgs( const char * host, int port, int argc, const char ** argv )
processArgs( const char * rpcurl, int argc, const char ** argv )
{
int c;
int status = EXIT_SUCCESS;
@ -1807,9 +1808,9 @@ processArgs( const char * host, int port, int argc, const char ** argv )
switch( c )
{
case 'a': /* add torrent */
if( sset != 0 ) status |= flush( host, port, &sset );
if( tadd != 0 ) status |= flush( host, port, &tadd );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
if( sset != 0 ) status |= flush( rpcurl, &sset );
if( tadd != 0 ) status |= flush( rpcurl, &tadd );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); }
tadd = tr_new0( tr_benc, 1 );
tr_bencInitDict( tadd, 3 );
tr_bencDictAddStr( tadd, "method", "torrent-add" );
@ -1841,8 +1842,8 @@ processArgs( const char * host, int port, int argc, const char ** argv )
break;
case 't': /* set current torrent */
if( tadd != 0 ) status |= flush( host, port, &tadd );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
if( tadd != 0 ) status |= flush( rpcurl, &tadd );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); }
tr_strlcpy( id, optarg, sizeof( id ) );
break;
@ -1884,8 +1885,8 @@ processArgs( const char * host, int port, int argc, const char ** argv )
args = tr_bencDictAddDict( top, ARGUMENTS, 0 );
fields = tr_bencDictAddList( args, "fields", 0 );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); }
switch( c )
{
case 'i': tr_bencDictAddInt( top, "tag", TAG_DETAILS );
@ -1918,7 +1919,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
default: assert( "unhandled value" && 0 );
}
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
}
else if( stepMode == MODE_SESSION_SET )
{
@ -2119,7 +2120,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencDictAddStr( args, "location", optarg );
tr_bencDictAddBool( args, "move", FALSE );
addIdArg( args, id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
}
@ -2131,7 +2132,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "session-get" );
tr_bencDictAddInt( top, "tag", TAG_SESSION );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 's': /* start */
@ -2143,7 +2144,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "torrent-start" );
addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
}
break;
}
@ -2156,7 +2157,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "torrent-stop" );
addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
}
break;
}
@ -2177,7 +2178,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_benc * top = tr_new0( tr_benc, 1 );
tr_bencInitDict( top, 1 );
tr_bencDictAddStr( top, "method", "blocklist-update" );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 921:
@ -2186,7 +2187,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "session-stats" );
tr_bencDictAddInt( top, "tag", TAG_STATS );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 962:
@ -2195,29 +2196,29 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "port-test" );
tr_bencDictAddInt( top, "tag", TAG_PORTTEST );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 600:
{
tr_benc * top;
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); }
top = tr_new0( tr_benc, 1 );
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "torrent-reannounce" );
addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 'v':
{
tr_benc * top;
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); }
top = tr_new0( tr_benc, 1 );
tr_bencInitDict( top, 2 );
tr_bencDictAddStr( top, "method", "torrent-verify" );
addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 'r':
@ -2230,7 +2231,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
args = tr_bencDictAddDict( top, ARGUMENTS, 2 );
tr_bencDictAddBool( args, "delete-local-data", c=='R' );
addIdArg( args, id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
case 960:
@ -2243,7 +2244,7 @@ processArgs( const char * host, int port, int argc, const char ** argv )
tr_bencDictAddStr( args, "location", optarg );
tr_bencDictAddBool( args, "move", TRUE );
addIdArg( args, id );
status |= flush( host, port, &top );
status |= flush( rpcurl, &top );
break;
}
default:
@ -2255,22 +2256,27 @@ processArgs( const char * host, int port, int argc, const char ** argv )
}
}
if( tadd != 0 ) status |= flush( host, port, &tadd );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
if( sset != 0 ) status |= flush( host, port, &sset );
if( tadd != 0 ) status |= flush( rpcurl, &tadd );
if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); }
if( sset != 0 ) status |= flush( rpcurl, &sset );
return status;
}
/* [host:port] or [host] or [port] */
/* [host:port] or [host] or [port] or [http://host:port/transmission/] */
static void
getHostAndPort( int * argc, char ** argv, char ** host, int * port )
getHostAndPortAndRpcUrl( int * argc, char ** argv,
char ** host, int * port, char ** rpcurl )
{
if( *argv[1] != '-' )
{
int i;
const char * s = argv[1];
const char * delim = strchr( s, ':' );
if( delim ) /* user passed in both host and port */
if( !strncmp(s, "http://", 7 ) ) /* user passed in full rpc url */
{
*rpcurl = tr_strdup_printf( "%s/rpc/", s );
}
else if( delim ) /* user passed in both host and port */
{
*host = tr_strndup( s, delim - s );
*port = atoi( delim + 1 );
@ -2296,6 +2302,7 @@ main( int argc, char ** argv )
{
int port = DEFAULT_PORT;
char * host = NULL;
char * rpcurl = NULL;
int exit_status = EXIT_SUCCESS;
if( argc < 2 ) {
@ -2307,12 +2314,15 @@ main( int argc, char ** argv )
tr_formatter_size_init( DISK_K,DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
tr_formatter_speed_init( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
getHostAndPort( &argc, argv, &host, &port );
getHostAndPortAndRpcUrl( &argc, argv, &host, &port, &rpcurl );
if( host == NULL )
host = tr_strdup( DEFAULT_HOST );
if( rpcurl == NULL )
rpcurl = tr_strdup_printf( "http://%s:%d%s", host, port, DEFAULT_URL );
exit_status = processArgs( host, port, argc, (const char**)argv );
exit_status = processArgs( rpcurl, argc, (const char**)argv );
tr_free( host );
tr_free( rpcurl );
return exit_status;
}

View File

@ -61,6 +61,7 @@ struct tr_rpc_server
tr_bool isPasswordEnabled;
tr_bool isWhitelistEnabled;
tr_port port;
char * url;
struct in_addr bindAddress;
struct evhttp * httpd;
tr_session * session;
@ -466,8 +467,6 @@ handle_web_client( struct evhttp_request * req,
{
const char * webClientDir = tr_getWebClientDir( server->session );
assert( !strncmp( req->uri, "/transmission/web/", 18 ) );
if( !webClientDir || !*webClientDir )
{
send_simple_response( req, HTTP_NOTFOUND,
@ -485,7 +484,7 @@ handle_web_client( struct evhttp_request * req,
char * pch;
char * subpath;
subpath = tr_strdup( req->uri + 18 );
subpath = tr_strdup( req->uri + strlen( server->url ) + 4 );
if(( pch = strchr( subpath, '?' )))
*pch = '\0';
@ -626,21 +625,19 @@ handle_request( struct evhttp_request * req, void * arg )
"Basic realm=\"" MY_REALM "\"" );
send_simple_response( req, 401, "Unauthorized User" );
}
else if( !strcmp( req->uri, "/transmission/web" )
|| !strcmp( req->uri, "/transmission/clutch" )
|| !strcmp( req->uri, "/" ) )
else if( strncmp( req->uri, server->url, strlen( server->url ) ) )
{
evhttp_add_header( req->output_headers, "Location", "/transmission/web/" );
const char * protocol = "http";
const char * host = evhttp_find_header( req->input_headers, "Host" );
char * location = tr_strdup_printf( "%s://%s%sweb/", protocol, host, server->url );
evhttp_add_header( req->output_headers, "Location", location );
send_simple_response( req, HTTP_MOVEPERM, NULL );
tr_free( location );
}
else if( !strncmp( req->uri, "/transmission/web/", 18 ) )
else if( !strncmp( req->uri + strlen( server->url ), "web/", 4 ) )
{
handle_web_client( req, server );
}
else if( !strncmp( req->uri, "/transmission/upload", 20 ) )
{
handle_upload( req, server );
}
#ifdef REQUIRE_SESSION_ID
else if( !test_session_id( server, req ) )
{
@ -662,10 +659,14 @@ handle_request( struct evhttp_request * req, void * arg )
tr_free( tmp );
}
#endif
else if( !strncmp( req->uri, "/transmission/rpc", 17 ) )
else if( !strncmp( req->uri + strlen( server->url ), "rpc", 3 ) )
{
handle_rpc( req, server );
}
else if( !strncmp( req->uri + strlen( server->url ), "upload", 6 ) )
{
handle_upload( req, server );
}
else
{
send_simple_response( req, HTTP_NOTFOUND, req->uri );
@ -763,15 +764,30 @@ tr_rpcGetPort( const tr_rpc_server * server )
}
void
tr_rpcSetWhitelist( tr_rpc_server * server,
const char * whitelistStr )
tr_rpcSetUrl( tr_rpc_server * server, const char * url )
{
char * tmp = server->url;
server->url = tr_strdup( url );
dbgmsg( "setting our URL to [%s]", server->url );
tr_free( tmp );
}
const char*
tr_rpcGetUrl( const tr_rpc_server * server )
{
return server->url ? server->url : "";
}
void
tr_rpcSetWhitelist( tr_rpc_server * server, const char * whitelistStr )
{
void * tmp;
const char * walk;
/* keep the string */
tr_free( server->whitelistStr );
tmp = server->whitelistStr;
server->whitelistStr = tr_strdup( whitelistStr );
tr_free( tmp );
/* clear out the old whitelist entries */
while(( tmp = tr_list_pop_front( &server->whitelist )))
@ -818,12 +834,12 @@ tr_rpcGetWhitelistEnabled( const tr_rpc_server * server )
****/
void
tr_rpcSetUsername( tr_rpc_server * server,
const char * username )
tr_rpcSetUsername( tr_rpc_server * server, const char * username )
{
tr_free( server->username );
char * tmp = server->username;
server->username = tr_strdup( username );
dbgmsg( "setting our Username to [%s]", server->username );
tr_free( tmp );
}
const char*
@ -890,6 +906,7 @@ closeServer( void * vserver )
if( s->isStreamInitialized )
deflateEnd( &s->stream );
#endif
tr_free( s->url );
tr_free( s->sessionId );
tr_free( s->whitelistStr );
tr_free( s->username );
@ -925,6 +942,10 @@ tr_rpcInit( tr_session * session, tr_benc * settings )
assert( found );
s->port = i;
found = tr_bencDictFindStr( settings, TR_PREFS_KEY_RPC_URL, &str );
assert( found );
s->url = tr_strdup( str );
found = tr_bencDictFindBool( settings, TR_PREFS_KEY_RPC_WHITELIST_ENABLED, &boolVal );
assert( found );
tr_rpcSetWhitelistEnabled( s, boolVal );
@ -959,7 +980,7 @@ tr_rpcInit( tr_session * session, tr_benc * settings )
if( s->isEnabled )
{
tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port %d" ), (int) s->port );
tr_ninf( MY_NAME, _( "Serving RPC and Web requests on port 127.0.0.1:%d%s" ), (int) s->port, s->url );
tr_runInEventThread( session, startServer, s );
if( s->isWhitelistEnabled )

View File

@ -34,6 +34,11 @@ void tr_rpcSetPort( tr_rpc_server * server,
tr_port tr_rpcGetPort( const tr_rpc_server * server );
void tr_rpcSetUrl( tr_rpc_server * server,
const char * url );
const char * tr_rpcGetUrl( const tr_rpc_server * server );
int tr_rpcSetTest( const tr_rpc_server * server,
const char * whitelist,
char ** allocme_errmsg );

View File

@ -284,6 +284,7 @@ tr_sessionGetDefaultSettings( const char * configDir UNUSED, tr_benc * d )
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 );
@ -345,6 +346,7 @@ tr_sessionGetSettings( tr_session * s, struct tr_benc * d )
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 ) );
@ -2326,6 +2328,23 @@ tr_sessionGetRPCPort( const tr_session * 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,

View File

@ -142,6 +142,7 @@ const char* tr_getDefaultDownloadDir( void );
#define TR_DEFAULT_OPEN_FILE_LIMIT_STR "32"
#define TR_DEFAULT_RPC_WHITELIST "127.0.0.1"
#define TR_DEFAULT_RPC_PORT_STR "9091"
#define TR_DEFAULT_RPC_URL_STR "/transmission/"
#define TR_DEFAULT_PEER_PORT_STR "51413"
#define TR_DEFAULT_PEER_SOCKET_TOS_STR "0"
#define TR_DEFAULT_PEER_LIMIT_GLOBAL_STR "240"
@ -190,6 +191,7 @@ const char* tr_getDefaultDownloadDir( void );
#define TR_PREFS_KEY_RPC_PASSWORD "rpc-password"
#define TR_PREFS_KEY_RPC_PORT "rpc-port"
#define TR_PREFS_KEY_RPC_USERNAME "rpc-username"
#define TR_PREFS_KEY_RPC_URL "rpc-url"
#define TR_PREFS_KEY_RPC_WHITELIST_ENABLED "rpc-whitelist-enabled"
#define TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME "script-torrent-done-filename"
#define TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED "script-torrent-done-enabled"
@ -421,6 +423,24 @@ void tr_sessionSetRPCPort( tr_session * session,
@see tr_sessionSetRPCPort */
tr_port tr_sessionGetRPCPort( const tr_session * session );
/**
* @brief Specify which base URL to use.
*
* @detail The RPC API is accessible under <url>/rpc, the web interface under
* <url>/web.
*
* @see tr_sessionGetRPCUrl
*/
void tr_sessionSetRPCUrl( tr_session * session,
const char * url );
/**
* @brief Get the base URL.
* @see tr_sessionInit()
* @see tr_sessionSetRPCUrl
*/
const char* tr_sessionGetRPCUrl( const tr_session * session );
/**
* @brief Specify a whitelist for remote RPC access
*

View File

@ -1805,7 +1805,7 @@ Transmission.prototype =
if ('' != $('#torrent_upload_url').val()) {
tr.remote.addTorrentByUrl($('#torrent_upload_url').val(), { paused: paused });
} else {
args.url = '/transmission/upload?paused=' + paused;
args.url = '../upload?paused=' + paused;
args.type = 'POST';
args.data = { 'X-Transmission-Session-Id' : tr.remote._token };
args.dataType = 'xml';

View File

@ -10,7 +10,7 @@ function RPC() { }
//Prefs.prototype = { }
// Constants
RPC._Root = '/transmission/rpc';
RPC._Root = '../rpc';
RPC._DaemonVersion = 'version';
RPC._Encryption = 'encryption';
RPC._EncryptionPreferred = 'preferred';