source: trunk/daemon/remote.c @ 6404

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

(rpc) to lower the bandwidth/cpu used up by very large torrent lists, allow finer-grained control over which fields to return in the `torrent-get' request.

  • Property svn:keywords set to Date Rev Author Id
File size: 31.1 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 6404 2008-07-26 14:47:07Z 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
175#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
176
177static const char * files_keys[] = {
178    "files", "name", "priorities", "wanted"
179};
180
181static const char * details_keys[] = {
182    "activityDate", "addedDate", "announceResponse", "announceURL",
183    "comment", "corruptEver", "creator", "dateCreated", "doneDate",
184    "downloadedEver", "errorString", "eta", "hashString", "haveUnchecked",
185    "haveValid", "id", "isPrivate", "lastAnnounceTime", "lastScrapeTime",
186    "leechers", "leftUntilDone", "name", "nextAnnounceTime", "nextScrapeTime",
187    "pieceCount", "pieceSize", "rateDownload", "rateUpload", "recheckProgress",
188    "scrapeResponse", "seeders", "sizeWhenDone", "sizeWhenDone", "startDate",
189    "status", "timesCompleted", "totalSize", "uploadedEver" 
190};
191
192static const char * list_keys[] = {
193    "downloadedEver", "eta", "id", "leftUntilDone", "name", "rateDownload",
194    "rateUpload", "sizeWhenDone", "status", "uploadedEver"
195};
196
197static void
198readargs( int argc, const char ** argv )
199{
200    int c;
201    int addingTorrents = 0;
202    const char * optarg;
203    char id[4096];
204
205    *id = '\0';
206
207    while(( c = tr_getopt( getUsage(), argc, argv, opts, &optarg )))
208    {
209        int i, n;
210        char buf[MAX_PATH_LENGTH];
211        int addArg = TRUE;
212        tr_benc top, *args, *fields;
213        tr_bencInitDict( &top, 3 );
214        args = tr_bencDictAddDict( &top, "arguments", 0 );
215
216        switch( c )
217        {
218            case TR_OPT_UNK:
219                      if( addingTorrents ) {
220                          char * tmp = getEncodedMetainfo( optarg );
221                          tr_bencDictAddStr( &top, "method", "torrent-add" );
222                          tr_bencDictAddStr( args, "metainfo", tmp );
223                          tr_free( tmp );
224                      } else {
225                          fprintf( stderr, "Unknown option: %s\n", optarg );
226                          addArg = FALSE;
227                      }
228                      break;
229            case 'a': addingTorrents = 1;
230                      addArg = FALSE;
231                      break;
232            case 'b': debug = 1;
233                      addArg = FALSE;
234                      break;
235            case 'd': tr_bencDictAddStr( &top, "method", "session-set" );
236                      tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
237                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
238                      break;
239            case 'D': tr_bencDictAddStr( &top, "method", "session-set" );
240                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
241                      break;
242            case 'f': tr_bencDictAddStr( &top, "method", "torrent-get" );
243                      tr_bencDictAddInt( &top, "tag", TAG_FILES );
244                      addIdArg( args, id );
245                      n = TR_N_ELEMENTS( files_keys );
246                      fields = tr_bencDictAddList( args, "fields", n );
247                      for( i=0; i<n; ++i )
248                          tr_bencListAddStr( fields, files_keys[i] );
249                      break;
250            case 'g': tr_bencDictAddStr( &top, "method", "torrent-set" );
251                      addIdArg( args, id );
252                      addFiles( args, "files-wanted", optarg );
253                      break;
254            case 'G': tr_bencDictAddStr( &top, "method", "torrent-set" );
255                      addIdArg( args, id );
256                      addFiles( args, "files-unwanted", optarg );
257                      break;
258            case 'i': tr_bencDictAddStr( &top, "method", "torrent-get" );
259                      tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
260                      addIdArg( args, id );
261                      n = TR_N_ELEMENTS( details_keys );
262                      fields = tr_bencDictAddList( args, "fields", n );
263                      for( i=0; i<n; ++i )
264                          tr_bencListAddStr( fields, details_keys[i] );
265                      break;
266            case 'l': tr_bencDictAddStr( &top, "method", "torrent-get" );
267                      tr_bencDictAddInt( &top, "tag", TAG_LIST );
268                      n = TR_N_ELEMENTS( list_keys );
269                      fields = tr_bencDictAddList( args, "fields", n );
270                      for( i=0; i<n; ++i )
271                          tr_bencListAddStr( fields, list_keys[i] );
272                      break;
273            case 'm': tr_bencDictAddStr( &top, "method", "session-set" );
274                      tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
275                      break;
276            case 'M': tr_bencDictAddStr( &top, "method", "session-set" );
277                      tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
278                      break;
279            case 'n': auth = tr_strdup( optarg );
280                      addArg = FALSE;
281                      break;
282            case 'p': tr_bencDictAddStr( &top, "method", "session-set" );
283                      tr_bencDictAddInt( args, "port", numarg( optarg ) );
284                      break;
285            case 'r': tr_bencDictAddStr( &top, "method", "torrent-remove" );
286                      addIdArg( args, id );
287                      break;
288            case 's': tr_bencDictAddStr( &top, "method", "torrent-start" );
289                      addIdArg( args, id );
290                      break;
291            case 'S': tr_bencDictAddStr( &top, "method", "torrent-stop" );
292                      addIdArg( args, id );
293                      break;
294            case 't': tr_strlcpy( id, optarg, sizeof( id ) );
295                      addArg = FALSE;
296                      break;
297            case 'u': tr_bencDictAddStr( &top, "method", "session-set" );
298                      tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
299                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
300                      break;
301            case 'U': tr_bencDictAddStr( &top, "method", "session-set" );
302                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
303                      break;
304            case 'v': tr_bencDictAddStr( &top, "method", "torrent-verify" );
305                      addIdArg( args, id );
306                      break;
307            case 'w': tr_bencDictAddStr( &top, "method", "session-set" );
308                      tr_bencDictAddStr( args, "download-dir",
309                                         absolutify(buf,sizeof(buf),optarg) );
310                      break;
311            case 'x': tr_bencDictAddStr( &top, "method", "session-set" );
312                      tr_bencDictAddInt( args, "pex-allowed", 1 );
313                      break;
314            case 'X': tr_bencDictAddStr( &top, "method", "session-set" );
315                      tr_bencDictAddInt( args, "pex-allowed", 0 );
316                      break;
317            case 900: tr_bencDictAddStr( &top, "method", "torrent-set" );
318                      addIdArg( args, id );
319                      addFiles( args, "priority-high", optarg );
320                      break;
321            case 901: tr_bencDictAddStr( &top, "method", "torrent-set" );
322                      addIdArg( args, id );
323                      addFiles( args, "priority-normal", optarg );
324                      break;
325            case 902: tr_bencDictAddStr( &top, "method", "torrent-set" );
326                      addIdArg( args, id );
327                      addFiles( args, "priority-low", optarg );
328                      break;
329            case 910: tr_bencDictAddStr( &top, "method", "session-set" );
330                      tr_bencDictAddStr( args, "encryption", "required" );
331                      break;
332            case 911: tr_bencDictAddStr( &top, "method", "session-set" );
333                      tr_bencDictAddStr( args, "encryption", "preferred" );
334                      break;
335            case 912: tr_bencDictAddStr( &top, "method", "session-set" );
336                      tr_bencDictAddStr( args, "encryption", "tolerated" );
337                      break;
338            case TR_OPT_ERR:
339                      fprintf( stderr, "invalid option\n" );
340                      showUsage( );
341                      break;
342            default:  fprintf( stderr, "got opt [%d]\n", (int)c );
343                      showUsage( );
344                      break;
345        }
346
347        if( addArg )
348            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
349        tr_bencFree( &top );
350    }
351}
352
353/* [host:port] or [host] or [port] */
354static void
355getHostAndPort( int * argc, char ** argv, char ** host, int * port )
356{
357    if( *argv[1] != '-' )
358    {
359        int i;
360        const char * s = argv[1];
361        const char * delim = strchr( s, ':' );
362        if( delim ) { /* user passed in both host and port */
363            *host = tr_strndup( s, delim-s );
364            *port = atoi( delim+1 );
365        } else {
366            char * end;
367            const int i = strtol( s, &end, 10 );
368            if( !*end ) /* user passed in a port */
369                *port = i;
370            else /* user passed in a host */
371                *host = tr_strdup( s );
372        }
373
374        *argc -= 1;
375        for( i=1; i<*argc; ++i )
376            argv[i] = argv[i+1];
377    }
378}
379
380static size_t
381writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
382{
383    const size_t byteCount = size * nmemb;
384    evbuffer_add( buf, ptr, byteCount );
385    return byteCount;
386}
387
388static void
389etaToString( char * buf, size_t buflen, int64_t eta )
390{
391         if( eta < 0 )           tr_snprintf( buf, buflen, "Unknown" );
392    else if( eta < 60 )          tr_snprintf( buf, buflen, "%"PRId64"sec", eta );
393    else if( eta < (60*60) )     tr_snprintf( buf, buflen, "%"PRId64" min", eta/60 );
394    else if( eta < (60*60*24) )  tr_snprintf( buf, buflen, "%"PRId64" hrs", eta/(60*60) );
395    else                         tr_snprintf( buf, buflen, "%"PRId64" days", eta/(60*60*24) );
396}
397
398#define KILOBYTE_FACTOR 1024.0
399#define MEGABYTE_FACTOR (1024.0 * 1024.0)
400#define GIGABYTE_FACTOR (1024.0 * 1024.0 * 1024.0)
401
402static char*
403strlratio( char * buf, double numerator, double denominator, size_t buflen )
404{
405    if( denominator )
406    {
407        const double ratio = numerator / denominator;
408        if( ratio < 10.0 )
409            tr_snprintf( buf, buflen, "%'.2f", ratio );
410        else if( ratio < 100.0 )
411            tr_snprintf( buf, buflen, "%'.1f", ratio );
412        else
413            tr_snprintf( buf, buflen, "%'.0f", ratio );
414    }
415    else if( numerator )
416        tr_strlcpy( buf, "Infinity", buflen );
417    else
418        tr_strlcpy( buf, "None", buflen );
419    return buf;
420}
421
422static char*
423strlsize( char * buf, int64_t size, size_t buflen )
424{
425    if( !size )
426        tr_strlcpy( buf, "None", buflen );
427    else if( size < (int64_t)KILOBYTE_FACTOR )
428        tr_snprintf( buf, buflen, "%'"PRId64" bytes", (int64_t)size );
429    else {
430        double displayed_size;
431        if (size < (int64_t)MEGABYTE_FACTOR) {
432            displayed_size = (double) size / KILOBYTE_FACTOR;
433            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
434        } else if (size < (int64_t)GIGABYTE_FACTOR) {
435            displayed_size = (double) size / MEGABYTE_FACTOR;
436            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
437        } else {
438            displayed_size = (double) size / GIGABYTE_FACTOR;
439            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
440        }
441    }
442    return buf;
443}
444
445static const char*
446torrentStatusToString( int i )
447{
448    switch( i )
449    {
450        case TR_STATUS_CHECK_WAIT: return "Will Verify";
451        case TR_STATUS_CHECK:      return "Verifying";
452        case TR_STATUS_DOWNLOAD:   return "Downloading";
453        case TR_STATUS_SEED:       return "Seeding";
454        case TR_STATUS_STOPPED:    return "Stopped";
455        default:                   return "Error";
456    }
457}
458
459static int
460isVerifying( int status )
461{
462    return ( ( status == TR_STATUS_CHECK_WAIT ) ||
463             ( status == TR_STATUS_CHECK ) );
464}
465
466static void
467printDetails( tr_benc * top )
468{
469    tr_benc *args, *torrents;
470
471    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
472        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
473    {
474        int ti, tCount;
475        for( ti=0, tCount=tr_bencListSize( torrents ); ti<tCount; ++ti )
476        {
477            tr_benc * t = tr_bencListChild( torrents, ti );
478            const char * str;
479            char buf[512];
480            char buf2[512];
481            int64_t i, j;
482
483            printf( "NAME\n" );
484            if( tr_bencDictFindInt( t, "id", &i ) )
485                printf( "  Id: %"PRId64"\n", i );
486            if( tr_bencDictFindStr( t, "name", &str ) )
487                printf( "  Name: %s\n", str );
488            if( tr_bencDictFindStr( t, "hashString", &str ) )
489                printf( "  Hash: %s\n", str );
490            printf( "\n" );
491
492            printf( "TRANSFER\n" );
493            if( tr_bencDictFindInt( t, "status", &i ) )
494            {
495                if( isVerifying( i ) && tr_bencDictFindStr( t, "recheckProgress", &str ) )
496                    tr_snprintf( buf, sizeof( buf ), " (%.0f%% Done)", 100.0*atof(str) );
497                else
498                    *buf = '\0';
499                printf( "  State: %s%s\n", torrentStatusToString( i ), buf );
500
501            }
502
503            if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) &&
504                tr_bencDictFindInt( t, "leftUntilDone", &j ) )
505            {
506                strlratio( buf, 100.0*(i-j), i, sizeof( buf ) );
507                printf( "  Percent Done: %s%%\n", buf );
508            }
509
510            if( tr_bencDictFindInt( t, "eta", &i ) ) {
511                etaToString( buf, sizeof( buf ), i );
512                printf( "  ETA: %s\n", buf );
513            }
514            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
515                printf( "  Download Speed: %.1f KB/s\n", i/1024.0 );
516            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
517                printf( "  Upload Speed: %.1f KB/s\n", i/1024.0 );
518            if( tr_bencDictFindInt( t, "haveUnchecked", &i ) &&
519                tr_bencDictFindInt( t, "haveValid", &j ) )
520            {
521                strlsize( buf, i+j, sizeof( buf ) );
522                strlsize( buf2, j, sizeof( buf2 ) );
523                printf( "  Have: %s (%s verified)\n", buf, buf2 );
524            }
525
526            if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) &&
527                tr_bencDictFindInt( t, "totalSize", &j ) )
528            {
529                strlsize( buf, j, sizeof( buf ) );
530                strlsize( buf2, i, sizeof( buf2 ) );
531                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
532            }
533            if( tr_bencDictFindInt( t, "downloadedEver", &i ) &&
534                tr_bencDictFindInt( t, "uploadedEver", &j ) ) {
535                strlsize( buf, i, sizeof( buf ) );
536                printf( "  Downloaded: %s\n", buf );
537                strlsize( buf, j, sizeof( buf ) );
538                printf( "  Uploaded: %s\n", buf );
539                strlratio( buf, j, i, sizeof( buf ) );
540                printf( "  Ratio: %s\n", buf );
541            }
542            if( tr_bencDictFindInt( t, "corruptEver", &i ) ) {
543                strlsize( buf, i, sizeof( buf ) );
544                printf( "  Corrupt DL: %s\n", buf );
545            }
546            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
547                printf( "  Error: %s\n", str );
548            printf( "\n" );
549           
550            printf( "HISTORY\n" );
551            if( tr_bencDictFindInt( t, "addedDate", &i ) && i ) {
552                const time_t tt = i;
553                printf( "  Date added:      %s", ctime( &tt ) );
554            }
555            if( tr_bencDictFindInt( t, "doneDate", &i ) && i ) {
556                const time_t tt = i;
557                printf( "  Date finished:   %s", ctime( &tt ) );
558            }
559            if( tr_bencDictFindInt( t, "startDate", &i ) && i ) {
560                const time_t tt = i;
561                printf( "  Date started:    %s", ctime( &tt ) );
562            }
563            if( tr_bencDictFindInt( t, "activityDate", &i ) && i ) {
564                const time_t tt = i;
565                printf( "  Latest activity: %s", ctime( &tt ) );
566            }
567            printf( "\n" );
568           
569            printf( "TRACKER\n" );
570            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) && i ) {
571                const time_t tt = i;
572                printf( "  Latest announce: %s", ctime( &tt ) );
573            }
574            if( tr_bencDictFindStr( t, "announceURL", &str ) )
575                printf( "  Announce URL: %s\n", str );
576            if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str )
577                printf( "  Announce response: %s\n", str );
578            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) && i ) {
579                const time_t tt = i;
580                printf( "  Next announce:   %s", ctime( &tt ) );
581            }
582            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) && i ) {
583                const time_t tt = i;
584                printf( "  Latest scrape:   %s", ctime( &tt ) );
585            }
586            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
587                printf( "  Scrape response: %s\n", str );
588            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) && i ) {
589                const time_t tt = i;
590                printf( "  Next scrape:     %s", ctime( &tt ) );
591            }
592            if( tr_bencDictFindInt( t, "seeders", &i ) &&
593                tr_bencDictFindInt( t, "leechers", &j ) )
594                printf( "  Tracker knows of %"PRId64" seeders and %"PRId64" leechers\n", i, j );
595            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
596                printf( "  Tracker has seen %"PRId64" clients complete this torrent\n", i );
597            printf( "\n" );
598
599            printf( "ORIGINS\n" );
600            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i ) {
601                const time_t tt = i;
602                printf( "  Date created: %s", ctime( &tt ) );
603            }
604            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
605                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
606            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
607                printf( "  Comment: %s\n", str );
608            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
609                printf( "  Creator: %s\n", str );
610            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
611                printf( "  Piece Count: %"PRId64"\n", i );
612            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
613                printf( "  Piece Size: %"PRId64"\n", i );
614        }
615    }
616}
617
618static void
619printFileList( tr_benc * top )
620{
621    tr_benc *args, *torrents;
622
623    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
624        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
625    {
626        int i, in;
627        for( i=0, in=tr_bencListSize( torrents ); i<in; ++i )
628        {
629            tr_benc * d = tr_bencListChild( torrents, i );
630            tr_benc *files, *priorities, *wanteds;
631            const char * name;
632            if( tr_bencDictFindStr( d, "name", &name ) &&
633                tr_bencDictFindList( d, "files", &files ) &&
634                tr_bencDictFindList( d, "priorities", &priorities ) &&
635                tr_bencDictFindList( d, "wanted", &wanteds ) )
636            {
637                int j=0, jn=tr_bencListSize(files);
638                printf( "%s (%d files):\n", name, jn );
639                printf("%3s  %4s %8s %3s %9s  %s\n", "#", "Done", "Priority", "Get", "Size", "Name" );
640                for( j=0, jn=tr_bencListSize( files ); j<jn; ++j )
641                {
642                    int64_t have;
643                    int64_t length;
644                    int64_t priority;
645                    int64_t wanted;
646                    const char * filename;
647                    tr_benc * file = tr_bencListChild( files, j );
648                    if( tr_bencDictFindInt( file, "length", &length ) &&
649                        tr_bencDictFindStr( file, "name", &filename ) &&
650                        tr_bencDictFindInt( file, "bytesCompleted", &have ) &&
651                        tr_bencGetInt( tr_bencListChild( priorities, j ), &priority ) &&
652                        tr_bencGetInt( tr_bencListChild( wanteds, j ), &wanted ) )
653                    {
654                        char sizestr[64];
655                        double percent = (double)have / length;
656                        strlsize( sizestr, length, sizeof( sizestr ) );
657                        const char * pristr;
658                        switch( priority ) {
659                            case TR_PRI_LOW:    pristr = "Low"; break;
660                            case TR_PRI_HIGH:   pristr = "High"; break;
661                            default:            pristr = "Normal"; break;
662                        }
663                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
664                                (j+1),
665                                (100.0*percent),
666                                pristr,
667                                (wanted?"Yes":"No"),
668                                sizestr,
669                                filename );
670                    }
671                }
672            }
673        }
674    }
675}
676
677static void
678printTorrentList( tr_benc * top )
679{
680    tr_benc *args, *list;
681
682    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
683        ( tr_bencDictFindList( args, "torrents", &list ) ) )
684    {
685        int i, n;
686        printf( "%-3s  %-4s  %-8s  %-5s  %-5s  %-5s  %-11s  %s\n",
687                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status", "Name" );
688        for( i=0, n=tr_bencListSize( list ); i<n; ++i )
689        {
690            int64_t id, eta, status, up, down;
691            int64_t sizeWhenDone, leftUntilDone;
692            int64_t upEver, downEver;
693            const char *name;
694            tr_benc * d = tr_bencListChild( list, i );
695            if(    tr_bencDictFindInt( d, "downloadedEver", &downEver )
696                && tr_bencDictFindInt( d, "eta", &eta )
697                && tr_bencDictFindInt( d, "id", &id )
698                && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
699                && tr_bencDictFindStr( d, "name", &name )
700                && tr_bencDictFindInt( d, "rateDownload", &down )
701                && tr_bencDictFindInt( d, "rateUpload", &up )
702                && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
703                && tr_bencDictFindInt( d, "status", &status )
704                && tr_bencDictFindInt( d, "uploadedEver", &upEver ) )
705            {
706                char etaStr[16];
707                if( leftUntilDone )
708                    etaToString( etaStr, sizeof( etaStr ), eta );
709                else
710                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
711                printf( "%3d  %3d%%  %-8s  %5.1f  %5.1f  %5.1f  %-11s  %s\n",
712                        (int)id,
713                        (int)(100.0*(sizeWhenDone-leftUntilDone)/sizeWhenDone),
714                        etaStr,
715                        up / 1024.0,
716                        down / 1024.0,
717                        (double)(downEver ? ((double)upEver/downEver) : 0.0),
718                        torrentStatusToString( status ),
719                        name );
720            }
721        }
722    }
723}
724
725static void
726processResponse( const char * host, int port,
727                 const void * response, size_t len )
728{
729    tr_benc top;
730
731    if( debug )
732        fprintf( stderr, "got response: [%*.*s]\n",
733                 (int)len, (int)len, (const char*) response );
734
735    if( tr_jsonParse( response, len, &top, NULL ) )
736       tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char*)response );
737    else
738    {
739        int64_t tag = -1;
740        const char * str;
741        tr_bencDictFindInt( &top, "tag", &tag );
742
743        switch( tag ) {
744            case TAG_FILES: printFileList( &top ); break;
745            case TAG_DETAILS: printDetails( &top ); break;
746            case TAG_LIST: printTorrentList( &top ); break;
747            default: if( tr_bencDictFindStr( &top, "result", &str ) ) 
748                         printf( "%s:%d responded: \"%s\"\n", host, port, str );
749        }
750
751        tr_bencFree( &top );
752    }
753}
754
755static void
756processRequests( const char * host, int port,
757                 const char ** reqs, int reqCount )
758{
759    int i;
760    CURL * curl;
761    struct evbuffer * buf = evbuffer_new( );
762    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
763
764    curl = curl_easy_init( );
765    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
766    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME"/"LONG_VERSION_STRING );
767    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
768    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
769    curl_easy_setopt( curl, CURLOPT_POST, 1 );
770    curl_easy_setopt( curl, CURLOPT_URL, url );
771    if( auth ) {
772        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
773        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
774    }
775
776    for( i=0; i<reqCount; ++i )
777    {
778        CURLcode res;
779        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
780        if( debug )
781            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
782        if(( res = curl_easy_perform( curl )))
783            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
784        else
785            processResponse( host, port, EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
786
787        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
788    }
789
790    /* cleanup */
791    tr_free( url );
792    evbuffer_free( buf );
793    curl_easy_cleanup( curl );
794}
795
796int
797main( int argc, char ** argv )
798{
799    int i;
800    int port = DEFAULT_PORT;
801    char * host = NULL;
802
803    if( argc < 2 )
804        showUsage( );
805
806    getHostAndPort( &argc, argv, &host, &port );
807    if( host == NULL )
808        host = tr_strdup( DEFAULT_HOST );
809
810    readargs( argc, (const char**)argv );
811    if( reqCount )
812        processRequests( host, port, (const char**)reqs, reqCount );
813    else
814        showUsage( );
815
816    for( i=0; i<reqCount; ++i )
817        tr_free( reqs[i] );
818
819    tr_free( host );
820    return 0;
821}
Note: See TracBrowser for help on using the repository browser.