source: trunk/daemon/remote.c @ 6249

Last change on this file since 6249 was 6249, checked in by charles, 14 years ago

#1055 transmission-remote --list outputs an empty list

  • Property svn:keywords set to Date Rev Author Id
File size: 16.3 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: remote.c 6249 2008-06-24 08:37:08Z charles $
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h> /* strcmp */
16
17#include <getopt.h>
18#include <unistd.h> /* getcwd */
19
20#include <libevent/event.h>
21#include <curl/curl.h>
22
23#include <libtransmission/transmission.h>
24#include <libtransmission/bencode.h>
25#include <libtransmission/rpc.h>
26#include <libtransmission/json.h>
27#include <libtransmission/utils.h>
28#include <libtransmission/version.h>
29
30#define MY_NAME "transmission-remote"
31#define DEFAULT_HOST "localhost"
32#define DEFAULT_PORT TR_DEFAULT_RPC_PORT
33
34enum { TAG_LIST };
35
36static void
37showUsage( void )
38{
39    puts( "Transmission "LONG_VERSION_STRING"  http://www.transmissionbt.com/\n"
40            "A fast and easy BitTorrent client\n"
41            "\n"
42            "Usage: "MY_NAME" [host] [options]\n"
43            "       "MY_NAME" [port] [options]\n"
44            "       "MY_NAME" [host:port] [options]\n"
45            "\n"
46            "Options:\n"
47            "  -a --add <torrent>        Add a torrent\n"
48            "  -c --encryption required  Require encryption for all peers\n"
49            "  -c --encryption preferred Prefer peers to use encryption\n"
50            "  -c --encryption tolerated Prefer peers to use plaintext\n"
51            "  -d --download-limit <int> Max download rate in KiB/s\n"
52            "  -D --download-unlimited   No download rate limit\n"
53            "  -e --enable-pex           Enable peer exchange\n"
54            "  -E --disable-pex          Disable peer exchange\n"
55            "  -f --folder <path>        Folder to set for new torrents\n"
56            "  -g --debug                Print debugging information\n"
57            "  -h --help                 Display this message and exit\n"
58            "  -l --list                 Long list of all torrent and status\n"
59            "  -m --port-mapping         Automatic port mapping via NAT-PMP or UPnP\n"
60            "  -M --no-port-mapping      Disable automatic port mapping\n"
61            "  -p --port <int>           Port to listen for incoming peers\n"
62            "  -r --remove <int>         Remove the torrent with the given ID\n"
63            "  -r --remove all           Remove all torrents\n"
64            "  -s --start <int>          Start the torrent with the given ID\n"
65            "  -s --start all            Start all stopped torrents\n"
66            "  -S --stop <int>           Stop the torrent with the given ID\n"
67            "  -S --stop all             Stop all running torrents\n"
68            "  -t --auth <user>:<pass>   Username and password for authentication\n"
69            "  -u --upload-limit <int>   Max upload rate in KiB/s\n"
70            "  -U --upload-unlimited     No upload rate limit\n"
71            "  -v --verify <id>          Verify the torrent's local data\n" );
72    exit( 0 );
73}
74
75static int
76numarg( const char * arg )
77{
78    char * end = NULL;
79    const long num = strtol( arg, &end, 10 );
80    if( *end ) {
81        fprintf( stderr, "Not a number: \"%s\"\n", arg );
82        showUsage( );
83    }
84    return num;
85}
86
87static char * reqs[256]; /* arbitrary max */
88static int reqCount = 0;
89static int debug = 0;
90static char * auth = NULL;
91
92static char*
93absolutify( char * buf, size_t len, const char * path )
94{
95    if( *path == '/' )
96        tr_strlcpy( buf, path, len );
97    else {
98        char cwd[MAX_PATH_LENGTH];
99        getcwd( cwd, sizeof( cwd ) );
100        tr_buildPath( buf, len, cwd, path, NULL );
101    }
102    return buf;
103}
104
105static char*
106getEncodedMetainfo( const char * filename )
107{
108    size_t len = 0;
109    uint8_t * buf = tr_loadFile( filename, &len );
110    char * b64 = tr_base64_encode( buf, len, NULL );
111    tr_free( buf );
112    return b64;
113}
114
115static void
116readargs( int argc, char ** argv )
117{
118    int opt;
119    char optstr[] = "a:c:d:DeEf:ghlmMp:r:s:S:t:u:Uv:";
120   
121    const struct option longopts[] =
122    {
123        { "add",                required_argument, NULL, 'a' },
124        { "encryption",         required_argument, NULL, 'c' },
125        { "download-limit",     required_argument, NULL, 'd' },
126        { "download-unlimited", no_argument,       NULL, 'D' },
127        { "enable-pex",         no_argument,       NULL, 'e' },
128        { "disable-pex",        no_argument,       NULL, 'E' },
129        { "folder",             required_argument, NULL, 'f' },
130        { "debug",              no_argument,       NULL, 'g' },
131        { "help",               no_argument,       NULL, 'h' },
132        { "list",               no_argument,       NULL, 'l' },
133        { "port-mapping",       no_argument,       NULL, 'm' },
134        { "no-port-mapping",    no_argument,       NULL, 'M' },
135        { "port",               required_argument, NULL, 'p' },
136        { "remove",             required_argument, NULL, 'r' },
137        { "start",              required_argument, NULL, 's' },
138        { "stop",               required_argument, NULL, 'S' },
139        { "auth",               required_argument, NULL, 't' },
140        { "upload-limit",       required_argument, NULL, 'u' },
141        { "upload-unlimited",   no_argument,       NULL, 'U' },
142        { "verify",             required_argument, NULL, 'v' },
143        { NULL, 0, NULL, 0 }
144    };
145
146    while((( opt = getopt_long( argc, argv, optstr, longopts, NULL ))) != -1 )
147    {
148        char * tmp;
149        char buf[MAX_PATH_LENGTH];
150        int addArg = TRUE;
151        int64_t fields = 0;
152        tr_benc top, *args;
153        tr_bencInitDict( &top, 3 );
154        args = tr_bencDictAddDict( &top, "arguments", 0 );
155
156        switch( opt )
157        {
158            case 'g': debug = 1;
159                      addArg = FALSE;
160                      break;
161            case 't': auth = tr_strdup( optarg );
162                      addArg = FALSE;
163                      break;
164            case 'h': showUsage( );
165                      addArg = FALSE;
166                      break;
167            case 'a': tr_bencDictAddStr( &top, "method", "torrent-add" );
168                      tr_bencDictAddStr( args, "metainfo", ((tmp=getEncodedMetainfo(optarg))) );
169                      tr_free( tmp );
170                      break;
171            case 'c': tr_bencDictAddStr( &top, "method", "session-set" );
172                      tr_bencDictAddStr( args, "encryption", optarg );
173                      break;
174            case 'd': tr_bencDictAddStr( &top, "method", "session-set" );
175                      tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
176                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
177                      break;
178            case 'D': tr_bencDictAddStr( &top, "method", "session-set" );
179                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
180                      break;
181            case 'u': tr_bencDictAddStr( &top, "method", "session-set" );
182                      tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
183                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
184                      break;
185            case 'U': tr_bencDictAddStr( &top, "method", "session-set" );
186                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
187                      break;
188            case 'e': tr_bencDictAddStr( &top, "method", "session-set" );
189                      tr_bencDictAddInt( args, "pex-allowed", 1 );
190                      break;
191            case 'E': tr_bencDictAddStr( &top, "method", "session-set" );
192                      tr_bencDictAddInt( args, "pex-allowed", 0 );
193                      break;
194            case 'f': tr_bencDictAddStr( &top, "method", "session-set" );
195                      tr_bencDictAddStr( args, "download-dir", absolutify(buf,sizeof(buf),optarg) );
196                      break;
197            case 'l': tr_bencDictAddStr( &top, "method", "torrent-get" );
198                      tr_bencDictAddInt( &top, "tag", TAG_LIST );
199                      fields = TR_RPC_TORRENT_FIELD_ID
200                             | TR_RPC_TORRENT_FIELD_ACTIVITY
201                             | TR_RPC_TORRENT_FIELD_SIZE;
202                      tr_bencDictAddInt( args, "fields", fields );
203                      break;
204            case 'm': tr_bencDictAddStr( &top, "method", "session-set" );
205                      tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
206                      break;
207            case 'M': tr_bencDictAddStr( &top, "method", "session-set" );
208                      tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
209                      break;
210            case 'p': tr_bencDictAddStr( &top, "method", "session-set" );
211                      tr_bencDictAddInt( args, "port", numarg( optarg ) );
212                      break;
213            case 'r': tr_bencDictAddStr( &top, "method", "torrent-remove" );
214                      if( strcmp( optarg, "all" ) )
215                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
216                      break;
217            case 's': tr_bencDictAddStr( &top, "method", "torrent-start" );
218                      if( strcmp( optarg, "all" ) )
219                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
220                      break;
221            case 'S': tr_bencDictAddStr( &top, "method", "torrent-stop" );
222                      if( strcmp( optarg, "all" ) )
223                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
224                      break;
225            case 'v': tr_bencDictAddStr( &top, "method", "torrent-verify" );
226                      if( strcmp( optarg, "all" ) )
227                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
228                      break;
229            default:
230                      showUsage( );
231                      addArg = FALSE;
232                      break;
233        }
234
235        if( addArg )
236            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
237        tr_bencFree( &top );
238    }
239}
240
241/* [host:port] or [host] or [port] */
242static void
243getHostAndPort( int * argc, char ** argv, char ** host, int * port )
244{
245    if( *argv[1] != '-' )
246    {
247        int i;
248        const char * s = argv[1];
249        const char * delim = strchr( s, ':' );
250        if( delim ) { /* user passed in both host and port */
251            *host = tr_strndup( s, delim-s );
252            *port = atoi( delim+1 );
253        } else {
254            char * end;
255            const int i = strtol( s, &end, 10 );
256            if( !*end ) /* user passed in a port */
257                *port = i;
258            else /* user passed in a host */
259                *host = tr_strdup( s );
260        }
261
262        *argc -= 1;
263        for( i=1; i<*argc; ++i )
264            argv[i] = argv[i+1];
265    }
266}
267
268static size_t
269writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
270{
271    const size_t byteCount = size * nmemb;
272    evbuffer_add( buf, ptr, byteCount );
273    return byteCount;
274}
275
276static void
277etaToString( char * buf, size_t buflen, int64_t eta )
278{
279         if( eta < 0 )           snprintf( buf, buflen, "Unknown" );
280    else if( eta < 60 )          snprintf( buf, buflen, "%"PRId64"sec", eta );
281    else if( eta < (60*60) )     snprintf( buf, buflen, "%"PRId64" min", eta/60 );
282    else if( eta < (60*60*24) )  snprintf( buf, buflen, "%"PRId64" hrs", eta/(60*60) );
283    else                         snprintf( buf, buflen, "%"PRId64" days", eta/(60*60*24) );
284}
285
286static const char*
287torrentStatusToString( int i )
288{
289    switch( i )
290    {
291        case TR_STATUS_CHECK_WAIT: return "Will Verify";
292        case TR_STATUS_CHECK:      return "Verifying";
293        case TR_STATUS_DOWNLOAD:   return "Downloading";
294        case TR_STATUS_SEED:       return "Seeding";
295        case TR_STATUS_STOPPED:    return "Stopped";
296        default:                   return "Error";
297    }
298}
299
300static void
301processResponse( const char * host, int port,
302                 const void * response, size_t len )
303{
304    tr_benc top;
305
306    if( debug )
307        fprintf( stderr, "got response: [%*.*s]\n",
308                 (int)len, (int)len, (const char*) response );
309
310    if( tr_jsonParse( response, len, &top, NULL ) )
311       tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char*)response );
312    else
313    {
314        tr_benc *args, *list;
315        int64_t tag = -1;
316        const char * str;
317        tr_bencDictFindInt( &top, "tag", &tag );
318
319        if( tr_bencDictFindStr( &top, "result", &str ) )
320            printf( "%s:%d responded: \"%s\"\n", host, port, str );
321
322        if( ( tag == TAG_LIST ) &&
323            ( tr_bencDictFindDict( &top, "arguments", &args ) ) &&
324            ( tr_bencDictFindList( args, "torrents", &list ) ) )
325        {
326            int i, n;
327            printf( "%-3s  %-4s  %-8s  %-5s  %-5s  %-5s  %-11s  %s\n",
328                    "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status", "Name" );
329            for( i=0, n=tr_bencListSize( list ); i<n; ++i )
330            {
331                int64_t id, eta, status, up, down, sizeWhenDone, leftUntilDone;
332                const char *name;
333                tr_benc * d = tr_bencListChild( list, i );
334                if(    tr_bencDictFindInt( d, "eta", &eta )
335                    && tr_bencDictFindInt( d, "id", &id )
336                    && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
337                    && tr_bencDictFindStr( d, "name", &name )
338                    && tr_bencDictFindInt( d, "rateDownload", &down )
339                    && tr_bencDictFindInt( d, "rateUpload", &up )
340                    && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
341                    && tr_bencDictFindInt( d, "status", &status ) )
342                {
343                    char etaStr[16];
344                    if( leftUntilDone )
345                        etaToString( etaStr, sizeof( etaStr ), eta );
346                    else
347                        snprintf( etaStr, sizeof( etaStr ), "Done" );
348                    printf( "%3d  %3d%%  %-8s  %5.1f  %5.1f  %5.1f  %-11s  %s\n",
349                            (int)id,
350                            (int)(100.0*(sizeWhenDone-leftUntilDone)/sizeWhenDone),
351                            etaStr,
352                            up / 1024.0,
353                            down / 1024.0,
354                            (double)(sizeWhenDone-leftUntilDone)/sizeWhenDone,
355                            torrentStatusToString( status ),
356                            name );
357                }
358            }
359        }
360
361        tr_bencFree( &top );
362    }
363}
364
365static void
366processRequests( const char * host, int port,
367                 const char ** reqs, int reqCount )
368{
369    int i;
370    CURL * curl;
371    struct evbuffer * buf = evbuffer_new( );
372    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
373
374    curl = curl_easy_init( );
375    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
376    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME"/"LONG_VERSION_STRING );
377    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
378    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
379    curl_easy_setopt( curl, CURLOPT_POST, 1 );
380    curl_easy_setopt( curl, CURLOPT_URL, url );
381    if( auth ) {
382        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
383        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
384    }
385
386    for( i=0; i<reqCount; ++i )
387    {
388        CURLcode res;
389        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
390        if( debug )
391            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
392        if(( res = curl_easy_perform( curl )))
393            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
394        else
395            processResponse( host, port, EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
396
397        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
398    }
399
400    /* cleanup */
401    tr_free( url );
402    evbuffer_free( buf );
403    curl_easy_cleanup( curl );
404}
405
406int
407main( int argc, char ** argv )
408{
409    int i;
410    int port = DEFAULT_PORT;
411    char * host = NULL;
412
413    if( argc < 2 )
414        showUsage( );
415
416    getHostAndPort( &argc, argv, &host, &port );
417    if( host == NULL )
418        host = tr_strdup( DEFAULT_HOST );
419
420    readargs( argc, argv );
421    if( reqCount )
422        processRequests( host, port, (const char**)reqs, reqCount );
423    else
424        showUsage( );
425
426    for( i=0; i<reqCount; ++i )
427        tr_free( reqs[i] );
428
429    tr_free( host );
430    return 0;
431}
Note: See TracBrowser for help on using the repository browser.