source: trunk/daemon/remote.c @ 6383

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

(rpc) 1099: transmission-remote confuses -ph, -pn and -pl (priorities) options with the -p (port) option

  • Property svn:keywords set to Date Rev Author Id
File size: 30.5 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 6383 2008-07-22 12:24:32Z charles $
11 */
12
13#include <errno.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h> /* strcmp */
17
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/tr-getopt.h>
28#include <libtransmission/utils.h>
29#include <libtransmission/version.h>
30
31#define MY_NAME "transmission-remote"
32#define DEFAULT_HOST "localhost"
33#define DEFAULT_PORT TR_DEFAULT_RPC_PORT
34
35enum { TAG_LIST, TAG_DETAILS, TAG_FILES };
36
37static const char*
38getUsage( void )
39{
40    return
41"Transmission "LONG_VERSION_STRING"  http://www.transmissionbt.com/\n"
42"A fast and easy BitTorrent client\n"
43"\n"
44"Usage: "MY_NAME" [host] [options]\n"
45"       "MY_NAME" [port] [options]\n"
46"       "MY_NAME" [host:port] [options]\n"
47"\n"
48"See the man page for detailed explanations and many examples.";
49}
50
51static tr_option opts[] =
52{
53    { 'a', "add",          "Add torrent files", "a", 0, NULL },
54    { 'b', "debug",        "Print debugging information", "b", 0, NULL },
55    { 'd', "downlimit",    "Set the maximum download speed in KB/s", "d", 1, "<speed>" },
56    { 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL },
57    { 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL },
58    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
59    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
60    { 'f', "files",        "List the current torrent's files", "f", 0, NULL },
61    { 'g', "get",          "Mark files for download", "g", 1, "<files>" },
62    { 'G', "no-get",       "Mark files for not downloading", "G", 1, "<files>" },
63    { 'h', "help",         "Show this help page and exit", "h", 0, NULL },
64    { 'i', "info",         "Show details of the current torrent(s)", "i", 0, NULL },
65    { 'l', "list",         "List all torrents", "l", 0, NULL },
66    { 'm', "portmap",      "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
67    { 'M', "no-portmap",   "Disable portmapping", "M", 0, NULL },
68    { 'n', "auth",         "Set username for authentication", "n", 1, "<auth>" },
69    { 'p', "port",
70      "Port for incoming peers (Default: "TR_DEFAULT_PORT_STR")",
71      "p", 1, "<port>" },
72    { 900, "priority-high", "Set the files' priorities as high", "ph", 1, "<files>" },
73    { 901, "priority-normal", "Set the files' priorities as normal", "pn", 1, "<files>" },
74    { 902, "priority-low", "Set the files' priorities as low", "pl", 1, "<files>" },
75    { 'r', "remove",       "Remove the current torrent(s)", "r", 0, NULL },
76    { 's', "start",        "Start the current torrent(s)", "s", 0, NULL },
77    { 'S', "stop",         "Stop the current torrent(s)", "S", 0, NULL },
78    { 't', "torrent",      "Set the current torrent(s)", "t", 1, "<torrent>" },
79    { 'u', "uplimit",      "Set the maximum upload speed in KB/s", "u", 1, "<speed>" },
80    { 'U', "no-uplimit",   "Don't limit the upload speed", "U", 0, NULL },
81    { 'v', "verify",       "Verify the current torrent(s)", "v", 0, NULL },
82    { 'w', "download-dir", "Set the default download folder", "w", 1, "<path>" },
83    { 'x', "pex",          "Enable peer exchange (PEX)", "x", 0, NULL },
84    { 'X', "no-pex",       "Disable peer exchange (PEX)", "X", 0, NULL },
85    { 0, NULL, NULL, NULL, 0, NULL }
86};
87
88static void
89showUsage( void )
90{
91    tr_getopt_usage( MY_NAME, getUsage(), opts );
92    exit( 0 );
93}
94
95static int
96numarg( const char * arg )
97{
98    char * end = NULL;
99    const long num = strtol( arg, &end, 10 );
100    if( *end ) {
101        fprintf( stderr, "Not a number: \"%s\"\n", arg );
102        showUsage( );
103    }
104    return num;
105}
106
107static char * reqs[256]; /* arbitrary max */
108static int reqCount = 0;
109static int debug = 0;
110static char * auth = NULL;
111
112static char*
113absolutify( char * buf, size_t len, const char * path )
114{
115    if( *path == '/' )
116        tr_strlcpy( buf, path, len );
117    else {
118        char cwd[MAX_PATH_LENGTH];
119        getcwd( cwd, sizeof( cwd ) );
120        tr_buildPath( buf, len, cwd, path, NULL );
121    }
122    return buf;
123}
124
125static char*
126getEncodedMetainfo( const char * filename )
127{
128    size_t len = 0;
129    uint8_t * buf = tr_loadFile( filename, &len );
130    char * b64 = tr_base64_encode( buf, len, NULL );
131    tr_free( buf );
132    return b64;
133}
134
135static void
136addIdArg( tr_benc * args, const char * id )
137{
138    if( !*id ) {
139        fprintf( stderr, "No torrent specified!  Please use the -t option first.\n" );
140        id = "-1"; /* no torrent will have this ID, so should be a no-op */
141    }
142    if( strcmp( id, "all" ) ) {
143        tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen(id) );
144    }
145}
146
147static void
148addFiles( tr_benc * args, const char * key, const char * arg )
149{
150    tr_benc * files = tr_bencDictAddList( args, key, 100 );
151
152    if( !*arg )
153    {
154        fprintf( stderr, "No files specified!\n" );
155        arg = "-1"; /* no file will have this index, so should be a no-op */
156    }
157    if( strcmp( arg, "all" ) )
158    {
159        const char * walk = arg;
160        while( *walk ) {
161            char * p;
162            unsigned long l;
163            errno = 0;
164            l = strtol( walk, &p, 10 );
165            if( errno )
166                break; 
167            tr_bencListAddInt( files, l - 1 );
168            if( *p != ',' )
169                break;
170            walk = p + 1;
171        }
172    }
173}
174
175static void
176readargs( int argc, const char ** argv )
177{
178    int c;
179    int addingTorrents = 0;
180    const char * optarg;
181    char id[4096];
182
183    *id = '\0';
184
185    while(( c = tr_getopt( getUsage(), argc, argv, opts, &optarg )))
186    {
187        char buf[MAX_PATH_LENGTH];
188        int addArg = TRUE;
189        tr_benc top, *args;
190        tr_bencInitDict( &top, 3 );
191        int64_t fields = 0;
192        args = tr_bencDictAddDict( &top, "arguments", 0 );
193
194        switch( c )
195        {
196            case TR_OPT_UNK:
197                      if( addingTorrents ) {
198                          char * tmp = getEncodedMetainfo( optarg );
199                          tr_bencDictAddStr( &top, "method", "torrent-add" );
200                          tr_bencDictAddStr( args, "metainfo", tmp );
201                          tr_free( tmp );
202                      } else {
203                          fprintf( stderr, "Unknown option: %s\n", optarg );
204                          addArg = FALSE;
205                      }
206                      break;
207            case 'a': addingTorrents = 1;
208                      addArg = FALSE;
209                      break;
210            case 'b': debug = 1;
211                      addArg = FALSE;
212                      break;
213            case 'd': tr_bencDictAddStr( &top, "method", "session-set" );
214                      tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
215                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
216                      break;
217            case 'D': tr_bencDictAddStr( &top, "method", "session-set" );
218                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
219                      break;
220            case 'f': tr_bencDictAddStr( &top, "method", "torrent-get" );
221                      tr_bencDictAddInt( &top, "tag", TAG_FILES );
222                      addIdArg( args, id );
223                      fields = TR_RPC_TORRENT_ID
224                             | TR_RPC_TORRENT_FILES
225                             | TR_RPC_TORRENT_PRIORITIES;
226                      tr_bencDictAddInt( args, "fields", fields );
227                      break;
228            case 'g': tr_bencDictAddStr( &top, "method", "torrent-set" );
229                      addIdArg( args, id );
230                      addFiles( args, "files-wanted", optarg );
231                      break;
232            case 'G': tr_bencDictAddStr( &top, "method", "torrent-set" );
233                      addIdArg( args, id );
234                      addFiles( args, "files-unwanted", optarg );
235                      break;
236            case 'i': tr_bencDictAddStr( &top, "method", "torrent-get" );
237                      tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
238                      addIdArg( args, id );
239                      fields = TR_RPC_TORRENT_ACTIVITY
240                          | TR_RPC_TORRENT_ANNOUNCE
241                          | TR_RPC_TORRENT_ERROR
242                          | TR_RPC_TORRENT_HISTORY
243                          | TR_RPC_TORRENT_ID
244                          | TR_RPC_TORRENT_INFO
245                          | TR_RPC_TORRENT_SCRAPE
246                          | TR_RPC_TORRENT_SIZE
247                          | TR_RPC_TORRENT_TRACKER_STATS;
248                      tr_bencDictAddInt( args, "fields", fields );
249                      break;
250            case 'l': tr_bencDictAddStr( &top, "method", "torrent-get" );
251                      tr_bencDictAddInt( &top, "tag", TAG_LIST );
252                      fields = TR_RPC_TORRENT_ID
253                             | TR_RPC_TORRENT_ACTIVITY
254                             | TR_RPC_TORRENT_HISTORY
255                             | TR_RPC_TORRENT_SIZE;
256                      tr_bencDictAddInt( args, "fields", fields );
257                      break;
258            case 'm': tr_bencDictAddStr( &top, "method", "session-set" );
259                      tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
260                      break;
261            case 'M': tr_bencDictAddStr( &top, "method", "session-set" );
262                      tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
263                      break;
264            case 'n': auth = tr_strdup( optarg );
265                      addArg = FALSE;
266                      break;
267            case 'p': tr_bencDictAddStr( &top, "method", "session-set" );
268                      tr_bencDictAddInt( args, "port", numarg( optarg ) );
269                      break;
270            case 'r': tr_bencDictAddStr( &top, "method", "torrent-remove" );
271                      addIdArg( args, id );
272                      break;
273            case 's': tr_bencDictAddStr( &top, "method", "torrent-start" );
274                      addIdArg( args, id );
275                      break;
276            case 'S': tr_bencDictAddStr( &top, "method", "torrent-stop" );
277                      addIdArg( args, id );
278                      break;
279            case 't': tr_strlcpy( id, optarg, sizeof( id ) );
280                      addArg = FALSE;
281                      break;
282            case 'u': tr_bencDictAddStr( &top, "method", "session-set" );
283                      tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
284                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
285                      break;
286            case 'U': tr_bencDictAddStr( &top, "method", "session-set" );
287                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
288                      break;
289            case 'v': tr_bencDictAddStr( &top, "method", "torrent-verify" );
290                      addIdArg( args, id );
291                      break;
292            case 'w': tr_bencDictAddStr( &top, "method", "session-set" );
293                      tr_bencDictAddStr( args, "download-dir",
294                                         absolutify(buf,sizeof(buf),optarg) );
295                      break;
296            case 'x': tr_bencDictAddStr( &top, "method", "session-set" );
297                      tr_bencDictAddInt( args, "pex-allowed", 1 );
298                      break;
299            case 'X': tr_bencDictAddStr( &top, "method", "session-set" );
300                      tr_bencDictAddInt( args, "pex-allowed", 0 );
301                      break;
302            case 900: tr_bencDictAddStr( &top, "method", "torrent-set" );
303                      addIdArg( args, id );
304                      addFiles( args, "priority-high", optarg );
305                      break;
306            case 901: tr_bencDictAddStr( &top, "method", "torrent-set" );
307                      addIdArg( args, id );
308                      addFiles( args, "priority-normal", optarg );
309                      break;
310            case 902: tr_bencDictAddStr( &top, "method", "torrent-set" );
311                      addIdArg( args, id );
312                      addFiles( args, "priority-low", optarg );
313                      break;
314            case 910: tr_bencDictAddStr( &top, "method", "session-set" );
315                      tr_bencDictAddStr( args, "encryption", "required" );
316                      break;
317            case 911: tr_bencDictAddStr( &top, "method", "session-set" );
318                      tr_bencDictAddStr( args, "encryption", "preferred" );
319                      break;
320            case 912: tr_bencDictAddStr( &top, "method", "session-set" );
321                      tr_bencDictAddStr( args, "encryption", "tolerated" );
322                      break;
323            case TR_OPT_ERR:
324                      fprintf( stderr, "invalid option\n" );
325                      showUsage( );
326                      break;
327            default:  fprintf( stderr, "got opt [%d]\n", (int)c );
328                      showUsage( );
329                      break;
330        }
331
332        if( addArg )
333            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
334        tr_bencFree( &top );
335    }
336}
337
338/* [host:port] or [host] or [port] */
339static void
340getHostAndPort( int * argc, char ** argv, char ** host, int * port )
341{
342    if( *argv[1] != '-' )
343    {
344        int i;
345        const char * s = argv[1];
346        const char * delim = strchr( s, ':' );
347        if( delim ) { /* user passed in both host and port */
348            *host = tr_strndup( s, delim-s );
349            *port = atoi( delim+1 );
350        } else {
351            char * end;
352            const int i = strtol( s, &end, 10 );
353            if( !*end ) /* user passed in a port */
354                *port = i;
355            else /* user passed in a host */
356                *host = tr_strdup( s );
357        }
358
359        *argc -= 1;
360        for( i=1; i<*argc; ++i )
361            argv[i] = argv[i+1];
362    }
363}
364
365static size_t
366writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
367{
368    const size_t byteCount = size * nmemb;
369    evbuffer_add( buf, ptr, byteCount );
370    return byteCount;
371}
372
373static void
374etaToString( char * buf, size_t buflen, int64_t eta )
375{
376         if( eta < 0 )           tr_snprintf( buf, buflen, "Unknown" );
377    else if( eta < 60 )          tr_snprintf( buf, buflen, "%"PRId64"sec", eta );
378    else if( eta < (60*60) )     tr_snprintf( buf, buflen, "%"PRId64" min", eta/60 );
379    else if( eta < (60*60*24) )  tr_snprintf( buf, buflen, "%"PRId64" hrs", eta/(60*60) );
380    else                         tr_snprintf( buf, buflen, "%"PRId64" days", eta/(60*60*24) );
381}
382
383#define KILOBYTE_FACTOR 1024.0
384#define MEGABYTE_FACTOR (1024.0 * 1024.0)
385#define GIGABYTE_FACTOR (1024.0 * 1024.0 * 1024.0)
386
387static char*
388strlratio( char * buf, double numerator, double denominator, size_t buflen )
389{
390    if( denominator )
391    {
392        const double ratio = numerator / denominator;
393        if( ratio < 10.0 )
394            tr_snprintf( buf, buflen, "%'.2f", ratio );
395        else if( ratio < 100.0 )
396            tr_snprintf( buf, buflen, "%'.1f", ratio );
397        else
398            tr_snprintf( buf, buflen, "%'.0f", ratio );
399    }
400    else if( numerator )
401        tr_strlcpy( buf, "Infinity", buflen );
402    else
403        tr_strlcpy( buf, "None", buflen );
404    return buf;
405}
406
407static char*
408strlsize( char * buf, int64_t size, size_t buflen )
409{
410    if( !size )
411        tr_strlcpy( buf, "None", buflen );
412    else if( size < (int64_t)KILOBYTE_FACTOR )
413        tr_snprintf( buf, buflen, "%'"PRId64" bytes", (int64_t)size );
414    else {
415        double displayed_size;
416        if (size < (int64_t)MEGABYTE_FACTOR) {
417            displayed_size = (double) size / KILOBYTE_FACTOR;
418            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
419        } else if (size < (int64_t)GIGABYTE_FACTOR) {
420            displayed_size = (double) size / MEGABYTE_FACTOR;
421            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
422        } else {
423            displayed_size = (double) size / GIGABYTE_FACTOR;
424            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
425        }
426    }
427    return buf;
428}
429
430static const char*
431torrentStatusToString( int i )
432{
433    switch( i )
434    {
435        case TR_STATUS_CHECK_WAIT: return "Will Verify";
436        case TR_STATUS_CHECK:      return "Verifying";
437        case TR_STATUS_DOWNLOAD:   return "Downloading";
438        case TR_STATUS_SEED:       return "Seeding";
439        case TR_STATUS_STOPPED:    return "Stopped";
440        default:                   return "Error";
441    }
442}
443
444static int
445isVerifying( int status )
446{
447    return ( ( status == TR_STATUS_CHECK_WAIT ) ||
448             ( status == TR_STATUS_CHECK ) );
449}
450
451static void
452printDetails( tr_benc * top )
453{
454    tr_benc *args, *torrents;
455
456    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
457        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
458    {
459        int ti, tCount;
460        for( ti=0, tCount=tr_bencListSize( torrents ); ti<tCount; ++ti )
461        {
462            tr_benc * t = tr_bencListChild( torrents, ti );
463            const char * str;
464            char buf[512];
465            char buf2[512];
466            int64_t i, j;
467
468            printf( "NAME\n" );
469            if( tr_bencDictFindInt( t, "id", &i ) )
470                printf( "  Id: %"PRId64"\n", i );
471            if( tr_bencDictFindStr( t, "name", &str ) )
472                printf( "  Name: %s\n", str );
473            if( tr_bencDictFindStr( t, "hashString", &str ) )
474                printf( "  Hash: %s\n", str );
475            printf( "\n" );
476
477            printf( "TRANSFER\n" );
478            if( tr_bencDictFindInt( t, "status", &i ) )
479            {
480                if( isVerifying( i ) && tr_bencDictFindStr( t, "recheckProgress", &str ) )
481                    tr_snprintf( buf, sizeof( buf ), " (%.0f%% Done)", 100.0*atof(str) );
482                else
483                    *buf = '\0';
484                printf( "  State: %s%s\n", torrentStatusToString( i ), buf );
485
486            }
487
488            if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) &&
489                tr_bencDictFindInt( t, "leftUntilDone", &j ) )
490            {
491                strlratio( buf, 100.0*(i-j), i, sizeof( buf ) );
492                printf( "  Percent Done: %s%%\n", buf );
493            }
494
495            if( tr_bencDictFindInt( t, "eta", &i ) ) {
496                etaToString( buf, sizeof( buf ), i );
497                printf( "  ETA: %s\n", buf );
498            }
499            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
500                printf( "  Download Speed: %.1f KB/s\n", i/1024.0 );
501            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
502                printf( "  Upload Speed: %.1f KB/s\n", i/1024.0 );
503            if( tr_bencDictFindInt( t, "haveUnchecked", &i ) &&
504                tr_bencDictFindInt( t, "haveValid", &j ) )
505            {
506                strlsize( buf, i+j, sizeof( buf ) );
507                strlsize( buf2, j, sizeof( buf2 ) );
508                printf( "  Have: %s (%s verified)\n", buf, buf2 );
509            }
510
511            if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) &&
512                tr_bencDictFindInt( t, "totalSize", &j ) )
513            {
514                strlsize( buf, j, sizeof( buf ) );
515                strlsize( buf2, i, sizeof( buf2 ) );
516                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
517            }
518            if( tr_bencDictFindInt( t, "downloadedEver", &i ) &&
519                tr_bencDictFindInt( t, "uploadedEver", &j ) ) {
520                strlsize( buf, i, sizeof( buf ) );
521                printf( "  Downloaded: %s\n", buf );
522                strlsize( buf, j, sizeof( buf ) );
523                printf( "  Uploaded: %s\n", buf );
524                strlratio( buf, j, i, sizeof( buf ) );
525                printf( "  Ratio: %s\n", buf );
526            }
527            if( tr_bencDictFindInt( t, "corruptEver", &i ) ) {
528                strlsize( buf, i, sizeof( buf ) );
529                printf( "  Corrupt DL: %s\n", buf );
530            }
531            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
532                printf( "  Error: %s\n", str );
533            printf( "\n" );
534           
535            printf( "HISTORY\n" );
536            if( tr_bencDictFindInt( t, "addedDate", &i ) && i ) {
537                const time_t tt = i;
538                printf( "  Date added:      %s", ctime( &tt ) );
539            }
540            if( tr_bencDictFindInt( t, "doneDate", &i ) && i ) {
541                const time_t tt = i;
542                printf( "  Date finished:   %s", ctime( &tt ) );
543            }
544            if( tr_bencDictFindInt( t, "startDate", &i ) && i ) {
545                const time_t tt = i;
546                printf( "  Date started:    %s", ctime( &tt ) );
547            }
548            if( tr_bencDictFindInt( t, "activityDate", &i ) && i ) {
549                const time_t tt = i;
550                printf( "  Latest activity: %s", ctime( &tt ) );
551            }
552            printf( "\n" );
553           
554            printf( "TRACKER\n" );
555            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) && i ) {
556                const time_t tt = i;
557                printf( "  Latest announce: %s", ctime( &tt ) );
558            }
559            if( tr_bencDictFindStr( t, "announceURL", &str ) )
560                printf( "  Announce URL: %s\n", str );
561            if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str )
562                printf( "  Announce response: %s\n", str );
563            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) && i ) {
564                const time_t tt = i;
565                printf( "  Next announce:   %s", ctime( &tt ) );
566            }
567            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) && i ) {
568                const time_t tt = i;
569                printf( "  Latest scrape:   %s", ctime( &tt ) );
570            }
571            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
572                printf( "  Scrape response: %s\n", str );
573            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) && i ) {
574                const time_t tt = i;
575                printf( "  Next scrape:     %s", ctime( &tt ) );
576            }
577            if( tr_bencDictFindInt( t, "seeders", &i ) &&
578                tr_bencDictFindInt( t, "leechers", &j ) )
579                printf( "  Tracker knows of %"PRId64" seeders and %"PRId64" leechers\n", i, j );
580            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
581                printf( "  Tracker has seen %"PRId64" clients complete this torrent\n", i );
582            printf( "\n" );
583
584            printf( "ORIGINS\n" );
585            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i ) {
586                const time_t tt = i;
587                printf( "  Date created: %s", ctime( &tt ) );
588            }
589            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
590                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
591            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
592                printf( "  Comment: %s\n", str );
593            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
594                printf( "  Creator: %s\n", str );
595            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
596                printf( "  Piece Count: %"PRId64"\n", i );
597            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
598                printf( "  Piece Size: %"PRId64"\n", i );
599        }
600    }
601}
602
603static void
604printFileList( tr_benc * top )
605{
606    tr_benc *args, *torrents;
607
608    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
609        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
610    {
611        int i, in;
612        for( i=0, in=tr_bencListSize( torrents ); i<in; ++i )
613        {
614            tr_benc * d = tr_bencListChild( torrents, i );
615            tr_benc *files, *priorities, *wanteds;
616            const char * name;
617            if( tr_bencDictFindStr( d, "name", &name ) &&
618                tr_bencDictFindList( d, "files", &files ) &&
619                tr_bencDictFindList( d, "priorities", &priorities ) &&
620                tr_bencDictFindList( d, "wanted", &wanteds ) )
621            {
622                int j=0, jn=tr_bencListSize(files);
623                printf( "%s (%d files):\n", name, jn );
624                printf("%3s  %4s %8s %3s %9s  %s\n", "#", "Done", "Priority", "Get", "Size", "Name" );
625                for( j=0, jn=tr_bencListSize( files ); j<jn; ++j )
626                {
627                    int64_t have;
628                    int64_t length;
629                    int64_t priority;
630                    int64_t wanted;
631                    const char * filename;
632                    tr_benc * file = tr_bencListChild( files, j );
633                    if( tr_bencDictFindInt( file, "length", &length ) &&
634                        tr_bencDictFindStr( file, "name", &filename ) &&
635                        tr_bencDictFindInt( file, "bytesCompleted", &have ) &&
636                        tr_bencGetInt( tr_bencListChild( priorities, j ), &priority ) &&
637                        tr_bencGetInt( tr_bencListChild( wanteds, j ), &wanted ) )
638                    {
639                        char sizestr[64];
640                        double percent = (double)have / length;
641                        strlsize( sizestr, length, sizeof( sizestr ) );
642                        const char * pristr;
643                        switch( priority ) {
644                            case TR_PRI_LOW:    pristr = "Low"; break;
645                            case TR_PRI_HIGH:   pristr = "High"; break;
646                            default:            pristr = "Normal"; break;
647                        }
648                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
649                                (j+1),
650                                (100.0*percent),
651                                pristr,
652                                (wanted?"Yes":"No"),
653                                sizestr,
654                                filename );
655                    }
656                }
657            }
658        }
659    }
660}
661
662static void
663printTorrentList( tr_benc * top )
664{
665    tr_benc *args, *list;
666
667    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
668        ( tr_bencDictFindList( args, "torrents", &list ) ) )
669    {
670        int i, n;
671        printf( "%-3s  %-4s  %-8s  %-5s  %-5s  %-5s  %-11s  %s\n",
672                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status", "Name" );
673        for( i=0, n=tr_bencListSize( list ); i<n; ++i )
674        {
675            int64_t id, eta, status, up, down;
676            int64_t sizeWhenDone, leftUntilDone;
677            int64_t upEver, downEver;
678            const char *name;
679            tr_benc * d = tr_bencListChild( list, i );
680            if(    tr_bencDictFindInt( d, "downloadedEver", &downEver )
681                && tr_bencDictFindInt( d, "eta", &eta )
682                && tr_bencDictFindInt( d, "id", &id )
683                && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
684                && tr_bencDictFindStr( d, "name", &name )
685                && tr_bencDictFindInt( d, "rateDownload", &down )
686                && tr_bencDictFindInt( d, "rateUpload", &up )
687                && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
688                && tr_bencDictFindInt( d, "status", &status )
689                && tr_bencDictFindInt( d, "uploadedEver", &upEver ) )
690            {
691                char etaStr[16];
692                if( leftUntilDone )
693                    etaToString( etaStr, sizeof( etaStr ), eta );
694                else
695                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
696                printf( "%3d  %3d%%  %-8s  %5.1f  %5.1f  %5.1f  %-11s  %s\n",
697                        (int)id,
698                        (int)(100.0*(sizeWhenDone-leftUntilDone)/sizeWhenDone),
699                        etaStr,
700                        up / 1024.0,
701                        down / 1024.0,
702                        (double)(downEver ? ((double)upEver/downEver) : 0.0),
703                        torrentStatusToString( status ),
704                        name );
705            }
706        }
707    }
708}
709
710static void
711processResponse( const char * host, int port,
712                 const void * response, size_t len )
713{
714    tr_benc top;
715
716    if( debug )
717        fprintf( stderr, "got response: [%*.*s]\n",
718                 (int)len, (int)len, (const char*) response );
719
720    if( tr_jsonParse( response, len, &top, NULL ) )
721       tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char*)response );
722    else
723    {
724        int64_t tag = -1;
725        const char * str;
726        tr_bencDictFindInt( &top, "tag", &tag );
727
728        if( tr_bencDictFindStr( &top, "result", &str ) )
729            printf( "%s:%d responded: \"%s\"\n", host, port, str );
730        switch( tag ) {
731            case TAG_FILES: printFileList( &top ); break;
732            case TAG_DETAILS: printDetails( &top ); break;
733            case TAG_LIST: printTorrentList( &top ); break;
734            default: break;
735        }
736
737        tr_bencFree( &top );
738    }
739}
740
741static void
742processRequests( const char * host, int port,
743                 const char ** reqs, int reqCount )
744{
745    int i;
746    CURL * curl;
747    struct evbuffer * buf = evbuffer_new( );
748    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
749
750    curl = curl_easy_init( );
751    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
752    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME"/"LONG_VERSION_STRING );
753    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
754    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
755    curl_easy_setopt( curl, CURLOPT_POST, 1 );
756    curl_easy_setopt( curl, CURLOPT_URL, url );
757    if( auth ) {
758        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
759        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
760    }
761
762    for( i=0; i<reqCount; ++i )
763    {
764        CURLcode res;
765        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
766        if( debug )
767            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
768        if(( res = curl_easy_perform( curl )))
769            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
770        else
771            processResponse( host, port, EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
772
773        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
774    }
775
776    /* cleanup */
777    tr_free( url );
778    evbuffer_free( buf );
779    curl_easy_cleanup( curl );
780}
781
782int
783main( int argc, char ** argv )
784{
785    int i;
786    int port = DEFAULT_PORT;
787    char * host = NULL;
788
789    if( argc < 2 )
790        showUsage( );
791
792    getHostAndPort( &argc, argv, &host, &port );
793    if( host == NULL )
794        host = tr_strdup( DEFAULT_HOST );
795
796    readargs( argc, (const char**)argv );
797    if( reqCount )
798        processRequests( host, port, (const char**)reqs, reqCount );
799    else
800        showUsage( );
801
802    for( i=0; i<reqCount; ++i )
803        tr_free( reqs[i] );
804
805    tr_free( host );
806    return 0;
807}
Note: See TracBrowser for help on using the repository browser.