source: trunk/daemon/remote.c @ 6481

Last change on this file since 6481 was 6481, checked in by charles, 13 years ago

(daemon) #1177: transmission-remote -a segaults on nonexistent filenames

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