source: trunk/daemon/remote.c @ 6291

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

(daemon) add file-listing capabilities to transmission-remote

  • Property svn:keywords set to Date Rev Author Id
File size: 20.0 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 6291 2008-07-07 03:38:22Z 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, TAG_DETAILS, TAG_FILES, TAG_PEERS };
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            "  -d --download-limit <int> Max download rate in KiB/s\n"
49            "  -D --download-unlimited   No download rate limit\n"
50            "  -e --encryption required  Require encryption for all peers\n"
51            "  -e --encryption preferred Prefer peers to use encryption\n"
52            "  -e --encryption tolerated Prefer peers to use plaintext\n"
53            "  -f --files <id>           Get a file list for the specified torrent\n"
54            "  -g --debug                Print debugging information\n"
55            "  -h --help                 Display this message and exit\n"
56            "  -l --list                 Long list of all torrent and status\n"
57            "  -m --port-mapping         Automatic port mapping via NAT-PMP or UPnP\n"
58            "  -M --no-port-mapping      Disable automatic port mapping\n"
59            "  -p --port <id>            Port to listen for incoming peers\n"
60            "  -r --remove <id>          Remove the torrent with the given ID\n"
61            "  -r --remove all           Remove all torrents\n"
62            "  -s --start <id>           Start the torrent with the given ID\n"
63            "  -s --start all            Start all stopped torrents\n"
64            "  -S --stop <id>            Stop the torrent with the given ID\n"
65            "  -S --stop all             Stop all running torrents\n"
66            "  -t --auth <user>:<pass>   Username and password for authentication\n"
67            "  -u --upload-limit <int>   Max upload rate in KiB/s\n"
68            "  -U --upload-unlimited     No upload rate limit\n"
69            "  -v --verify <id>          Verify the torrent's local data\n"
70            "  -w --download-dir <path>  Folder to set for new torrents\n"
71            "  -x --enable-pex           Enable peer exchange\n"
72            "  -X --disable-pex          Disable peer exchange\n" );
73    exit( 0 );
74}
75
76static int
77numarg( const char * arg )
78{
79    char * end = NULL;
80    const long num = strtol( arg, &end, 10 );
81    if( *end ) {
82        fprintf( stderr, "Not a number: \"%s\"\n", arg );
83        showUsage( );
84    }
85    return num;
86}
87
88static char * reqs[256]; /* arbitrary max */
89static int reqCount = 0;
90static int debug = 0;
91static char * auth = NULL;
92
93static char*
94absolutify( char * buf, size_t len, const char * path )
95{
96    if( *path == '/' )
97        tr_strlcpy( buf, path, len );
98    else {
99        char cwd[MAX_PATH_LENGTH];
100        getcwd( cwd, sizeof( cwd ) );
101        tr_buildPath( buf, len, cwd, path, NULL );
102    }
103    return buf;
104}
105
106static char*
107getEncodedMetainfo( const char * filename )
108{
109    size_t len = 0;
110    uint8_t * buf = tr_loadFile( filename, &len );
111    char * b64 = tr_base64_encode( buf, len, NULL );
112    tr_free( buf );
113    return b64;
114}
115
116static void
117readargs( int argc, char ** argv )
118{
119    int opt;
120    char optstr[] = "a:d:De:f:ghlmMp:r:s:S:t:u:Uv:w:xX";
121   
122    const struct option longopts[] =
123    {
124        { "add",                required_argument, NULL, 'a' },
125        { "download-limit",     required_argument, NULL, 'd' },
126        { "download-unlimited", no_argument,       NULL, 'D' },
127        { "encryption",         required_argument, NULL, 'e' },
128        { "files",              required_argument, NULL, 'f' },
129        { "debug",              no_argument,       NULL, 'g' },
130        { "help",               no_argument,       NULL, 'h' },
131        { "list",               no_argument,       NULL, 'l' },
132        { "port-mapping",       no_argument,       NULL, 'm' },
133        { "no-port-mapping",    no_argument,       NULL, 'M' },
134        { "port",               required_argument, NULL, 'p' },
135        { "remove",             required_argument, NULL, 'r' },
136        { "start",              required_argument, NULL, 's' },
137        { "stop",               required_argument, NULL, 'S' },
138        { "auth",               required_argument, NULL, 't' },
139        { "upload-limit",       required_argument, NULL, 'u' },
140        { "upload-unlimited",   no_argument,       NULL, 'U' },
141        { "verify",             required_argument, NULL, 'v' },
142        { "download-dir",       required_argument, NULL, 'w' },
143        { "enable-pex",         no_argument,       NULL, 'x' },
144        { "disable-pex",        no_argument,       NULL, 'X' },
145        { NULL, 0, NULL, 0 }
146    };
147
148    while((( opt = getopt_long( argc, argv, optstr, longopts, NULL ))) != -1 )
149    {
150        char * tmp;
151        char buf[MAX_PATH_LENGTH];
152        int addArg = TRUE;
153        int64_t fields = 0;
154        tr_benc top, *args;
155        tr_bencInitDict( &top, 3 );
156        args = tr_bencDictAddDict( &top, "arguments", 0 );
157
158        switch( opt )
159        {
160            case 'g': debug = 1;
161                      addArg = FALSE;
162                      break;
163            case 't': auth = tr_strdup( optarg );
164                      addArg = FALSE;
165                      break;
166            case 'h': showUsage( );
167                      addArg = FALSE;
168                      break;
169            case 'a': tr_bencDictAddStr( &top, "method", "torrent-add" );
170                      tr_bencDictAddStr( args, "metainfo", ((tmp=getEncodedMetainfo(optarg))) );
171                      tr_free( tmp );
172                      break;
173            case 'e': tr_bencDictAddStr( &top, "method", "session-set" );
174                      tr_bencDictAddStr( args, "encryption", optarg );
175                      break;
176            case 'f': tr_bencDictAddStr( &top, "method", "torrent-get" );
177                      tr_bencDictAddInt( &top, "tag", TAG_FILES );
178                      tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
179                      fields = TR_RPC_TORRENT_FIELD_ID
180                             | TR_RPC_TORRENT_FIELD_FILES
181                             | TR_RPC_TORRENT_FIELD_PRIORITIES;
182                      tr_bencDictAddInt( args, "fields", fields );
183                      break;
184            case 'd': tr_bencDictAddStr( &top, "method", "session-set" );
185                      tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
186                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
187                      break;
188            case 'D': tr_bencDictAddStr( &top, "method", "session-set" );
189                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
190                      break;
191            case 'u': tr_bencDictAddStr( &top, "method", "session-set" );
192                      tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
193                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
194                      break;
195            case 'U': tr_bencDictAddStr( &top, "method", "session-set" );
196                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
197                      break;
198            case 'l': tr_bencDictAddStr( &top, "method", "torrent-get" );
199                      tr_bencDictAddInt( &top, "tag", TAG_LIST );
200                      fields = TR_RPC_TORRENT_FIELD_ID
201                             | TR_RPC_TORRENT_FIELD_ACTIVITY
202                             | TR_RPC_TORRENT_FIELD_SIZE;
203                      tr_bencDictAddInt( args, "fields", fields );
204                      break;
205            case 'm': tr_bencDictAddStr( &top, "method", "session-set" );
206                      tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
207                      break;
208            case 'M': tr_bencDictAddStr( &top, "method", "session-set" );
209                      tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
210                      break;
211            case 'p': tr_bencDictAddStr( &top, "method", "session-set" );
212                      tr_bencDictAddInt( args, "port", numarg( optarg ) );
213                      break;
214            case 'r': tr_bencDictAddStr( &top, "method", "torrent-remove" );
215                      if( strcmp( optarg, "all" ) )
216                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
217                      break;
218            case 's': tr_bencDictAddStr( &top, "method", "torrent-start" );
219                      if( strcmp( optarg, "all" ) )
220                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
221                      break;
222            case 'S': tr_bencDictAddStr( &top, "method", "torrent-stop" );
223                      if( strcmp( optarg, "all" ) )
224                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
225                      break;
226            case 'v': tr_bencDictAddStr( &top, "method", "torrent-verify" );
227                      if( strcmp( optarg, "all" ) )
228                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
229                      break;
230            case 'w': tr_bencDictAddStr( &top, "method", "session-set" );
231                      tr_bencDictAddStr( args, "download-dir", absolutify(buf,sizeof(buf),optarg) );
232                      break;
233            case 'x': tr_bencDictAddStr( &top, "method", "session-set" );
234                      tr_bencDictAddInt( args, "pex-allowed", 1 );
235                      break;
236            case 'X': tr_bencDictAddStr( &top, "method", "session-set" );
237                      tr_bencDictAddInt( args, "pex-allowed", 0 );
238                      break;
239            default:
240                      showUsage( );
241                      addArg = FALSE;
242                      break;
243        }
244
245        if( addArg )
246            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
247        tr_bencFree( &top );
248    }
249}
250
251/* [host:port] or [host] or [port] */
252static void
253getHostAndPort( int * argc, char ** argv, char ** host, int * port )
254{
255    if( *argv[1] != '-' )
256    {
257        int i;
258        const char * s = argv[1];
259        const char * delim = strchr( s, ':' );
260        if( delim ) { /* user passed in both host and port */
261            *host = tr_strndup( s, delim-s );
262            *port = atoi( delim+1 );
263        } else {
264            char * end;
265            const int i = strtol( s, &end, 10 );
266            if( !*end ) /* user passed in a port */
267                *port = i;
268            else /* user passed in a host */
269                *host = tr_strdup( s );
270        }
271
272        *argc -= 1;
273        for( i=1; i<*argc; ++i )
274            argv[i] = argv[i+1];
275    }
276}
277
278static size_t
279writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
280{
281    const size_t byteCount = size * nmemb;
282    evbuffer_add( buf, ptr, byteCount );
283    return byteCount;
284}
285
286static void
287etaToString( char * buf, size_t buflen, int64_t eta )
288{
289         if( eta < 0 )           snprintf( buf, buflen, "Unknown" );
290    else if( eta < 60 )          snprintf( buf, buflen, "%"PRId64"sec", eta );
291    else if( eta < (60*60) )     snprintf( buf, buflen, "%"PRId64" min", eta/60 );
292    else if( eta < (60*60*24) )  snprintf( buf, buflen, "%"PRId64" hrs", eta/(60*60) );
293    else                         snprintf( buf, buflen, "%"PRId64" days", eta/(60*60*24) );
294}
295
296#define KILOBYTE_FACTOR 1024.0
297#define MEGABYTE_FACTOR (1024.0 * 1024.0)
298#define GIGABYTE_FACTOR (1024.0 * 1024.0 * 1024.0)
299
300static char*
301strlsize( char * buf, int64_t size, size_t buflen )
302{
303    if( !size )
304        tr_strlcpy( buf, "None", buflen );
305    else if( size < (int64_t)KILOBYTE_FACTOR )
306        snprintf( buf, buflen, "%'"PRId64" bytes", (int64_t)size );
307    else {
308        double displayed_size;
309        if (size < (int64_t)MEGABYTE_FACTOR) {
310            displayed_size = (double) size / KILOBYTE_FACTOR;
311            snprintf( buf, buflen, "%'.1f KB", displayed_size );
312        } else if (size < (int64_t)GIGABYTE_FACTOR) {
313            displayed_size = (double) size / MEGABYTE_FACTOR;
314            snprintf( buf, buflen, "%'.1f MB", displayed_size );
315        } else {
316            displayed_size = (double) size / GIGABYTE_FACTOR;
317            snprintf( buf, buflen, "%'.1f GB", displayed_size );
318        }
319    }
320    return buf;
321}
322
323static const char*
324torrentStatusToString( int i )
325{
326    switch( i )
327    {
328        case TR_STATUS_CHECK_WAIT: return "Will Verify";
329        case TR_STATUS_CHECK:      return "Verifying";
330        case TR_STATUS_DOWNLOAD:   return "Downloading";
331        case TR_STATUS_SEED:       return "Seeding";
332        case TR_STATUS_STOPPED:    return "Stopped";
333        default:                   return "Error";
334    }
335}
336
337static void
338printFileList( tr_benc * top )
339{
340    tr_benc *args, *torrents;
341
342    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
343        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
344    {
345        int i, in;
346        for( i=0, in=tr_bencListSize( torrents ); i<in; ++i )
347        {
348            tr_benc * d = tr_bencListChild( torrents, i );
349            tr_benc *files, *priorities, *wanteds;
350            const char * name;
351            if( tr_bencDictFindStr( d, "name", &name ) &&
352                tr_bencDictFindList( d, "files", &files ) &&
353                tr_bencDictFindList( d, "priorities", &priorities ) &&
354                tr_bencDictFindList( d, "wanted", &wanteds ) )
355            {
356                int j=0, jn=tr_bencListSize(files);
357                printf( "%s (%d files):\n", name, jn );
358                printf("%3s  %8s %3s %9s  %s\n", "#", "Priority", "Get", "Size", "Name" );
359                for( j=0, jn=tr_bencListSize( files ); j<jn; ++j )
360                {
361                    int64_t length;
362                    int64_t priority;
363                    int64_t wanted;
364                    const char * filename;
365                    tr_benc * file = tr_bencListChild( files, j );
366                    if( tr_bencDictFindInt( file, "length", &length ) &&
367                        tr_bencDictFindStr( file, "name", &filename ) &&
368                        tr_bencGetInt( tr_bencListChild( priorities, j ), &priority ) &&
369                        tr_bencGetInt( tr_bencListChild( wanteds, j ), &wanted ) )
370                    {
371                        char sizestr[64];
372                        strlsize( sizestr, length, sizeof( sizestr ) );
373                        const char * pristr;
374                        switch( priority ) {
375                            case TR_PRI_LOW:    pristr = "Low"; break;
376                            case TR_PRI_HIGH:   pristr = "High"; break;
377                            default:            pristr = "Normal"; break;
378                        }
379                        printf( "%3d: %-8s %-3s %9s  %s\n", (j+1), pristr, (wanted?"Yes":"No"), sizestr, filename );
380                    }
381                }
382            }
383        }
384    }
385}
386
387static void
388printTorrentList( tr_benc * top )
389{
390    tr_benc *args, *list;
391
392    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
393        ( tr_bencDictFindList( args, "torrents", &list ) ) )
394    {
395        int i, n;
396        printf( "%-3s  %-4s  %-8s  %-5s  %-5s  %-5s  %-11s  %s\n",
397                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status", "Name" );
398        for( i=0, n=tr_bencListSize( list ); i<n; ++i )
399        {
400            int64_t id, eta, status, up, down, sizeWhenDone, leftUntilDone;
401            const char *name;
402            tr_benc * d = tr_bencListChild( list, i );
403            if(    tr_bencDictFindInt( d, "eta", &eta )
404                && tr_bencDictFindInt( d, "id", &id )
405                && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
406                && tr_bencDictFindStr( d, "name", &name )
407                && tr_bencDictFindInt( d, "rateDownload", &down )
408                && tr_bencDictFindInt( d, "rateUpload", &up )
409                && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
410                && tr_bencDictFindInt( d, "status", &status ) )
411            {
412                char etaStr[16];
413                if( leftUntilDone )
414                    etaToString( etaStr, sizeof( etaStr ), eta );
415                else
416                    snprintf( etaStr, sizeof( etaStr ), "Done" );
417                printf( "%3d  %3d%%  %-8s  %5.1f  %5.1f  %5.1f  %-11s  %s\n",
418                        (int)id,
419                        (int)(100.0*(sizeWhenDone-leftUntilDone)/sizeWhenDone),
420                        etaStr,
421                        up / 1024.0,
422                        down / 1024.0,
423                        (double)(sizeWhenDone-leftUntilDone)/sizeWhenDone,
424                        torrentStatusToString( status ),
425                        name );
426            }
427        }
428    }
429}
430
431static void
432processResponse( const char * host, int port,
433                 const void * response, size_t len )
434{
435    tr_benc top;
436
437    if( debug )
438        fprintf( stderr, "got response: [%*.*s]\n",
439                 (int)len, (int)len, (const char*) response );
440
441    if( tr_jsonParse( response, len, &top, NULL ) )
442       tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char*)response );
443    else
444    {
445        int64_t tag = -1;
446        const char * str;
447        tr_bencDictFindInt( &top, "tag", &tag );
448
449        if( tr_bencDictFindStr( &top, "result", &str ) )
450            printf( "%s:%d responded: \"%s\"\n", host, port, str );
451        if( tag == TAG_FILES )
452            printFileList( &top );
453        if( tag == TAG_LIST )
454            printTorrentList( &top );
455
456        tr_bencFree( &top );
457    }
458}
459
460static void
461processRequests( const char * host, int port,
462                 const char ** reqs, int reqCount )
463{
464    int i;
465    CURL * curl;
466    struct evbuffer * buf = evbuffer_new( );
467    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
468
469    curl = curl_easy_init( );
470    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
471    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME"/"LONG_VERSION_STRING );
472    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
473    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
474    curl_easy_setopt( curl, CURLOPT_POST, 1 );
475    curl_easy_setopt( curl, CURLOPT_URL, url );
476    if( auth ) {
477        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
478        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
479    }
480
481    for( i=0; i<reqCount; ++i )
482    {
483        CURLcode res;
484        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
485        if( debug )
486            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
487        if(( res = curl_easy_perform( curl )))
488            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
489        else
490            processResponse( host, port, EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
491
492        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
493    }
494
495    /* cleanup */
496    tr_free( url );
497    evbuffer_free( buf );
498    curl_easy_cleanup( curl );
499}
500
501int
502main( int argc, char ** argv )
503{
504    int i;
505    int port = DEFAULT_PORT;
506    char * host = NULL;
507
508    if( argc < 2 )
509        showUsage( );
510
511    getHostAndPort( &argc, argv, &host, &port );
512    if( host == NULL )
513        host = tr_strdup( DEFAULT_HOST );
514
515    readargs( argc, argv );
516    if( reqCount )
517        processRequests( host, port, (const char**)reqs, reqCount );
518    else
519        showUsage( );
520
521    for( i=0; i<reqCount; ++i )
522        tr_free( reqs[i] );
523
524    tr_free( host );
525    return 0;
526}
Note: See TracBrowser for help on using the repository browser.