source: trunk/daemon/remote.c @ 6407

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

(daemon) #1107: transmission-remote -t[n] -i should display webseeding info

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