source: trunk/daemon/remote.c @ 7038

Last change on this file since 7038 was 7038, checked in by charles, 12 years ago

(daemon) #1420: transmission-remote --peers doesn't work right

  • Property svn:keywords set to Date Rev Author Id
File size: 38.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 7038 2008-11-04 17:04:41Z charles $
11 */
12
13#include <errno.h>
14#include <stdio.h>
15#include <stdlib.h>
16#include <string.h> /* strcmp */
17
18#include <libevent/event.h>
19#include <curl/curl.h>
20
21#include <libtransmission/transmission.h>
22#include <libtransmission/bencode.h>
23#include <libtransmission/rpcimpl.h>
24#include <libtransmission/json.h>
25#include <libtransmission/tr-getopt.h>
26#include <libtransmission/utils.h>
27#include <libtransmission/version.h>
28
29#define MY_NAME "transmission-remote"
30#define DEFAULT_HOST "localhost"
31#define DEFAULT_PORT TR_DEFAULT_RPC_PORT
32
33enum { TAG_LIST, TAG_DETAILS, TAG_FILES, TAG_PEERS };
34
35static const char*
36getUsage( void )
37{
38    return
39        "Transmission " LONG_VERSION_STRING
40        "  http://www.transmissionbt.com/\n"
41        "A fast and easy BitTorrent client\n"
42        "\n"
43        "Usage: " MY_NAME
44        " [host] [options]\n"
45        "       "
46        MY_NAME " [port] [options]\n"
47                "       "
48        MY_NAME " [host:port] [options]\n"
49                "\n"
50                "See the man page for detailed explanations and many examples.";
51}
52
53static tr_option opts[] =
54{
55    { 'a', "add",                  "Add torrent files",
56      "a",  0, NULL                  },
57    { 'b', "debug",                "Print debugging information",
58      "b",  0, NULL                  },
59    { 'd', "downlimit",            "Set the maximum download speed in KB/s",
60      "d",  1, "<speed>"             },
61    { 'D', "no-downlimit",         "Don't limit the download speed",
62      "D",  0, NULL                  },
63    { 910, "encryption-required",  "Encrypt all peer connections",
64      "er", 0, NULL                  },
65    { 911, "encryption-preferred", "Prefer encrypted peer connections",
66      "ep", 0, NULL                  },
67    { 912, "encryption-tolerated", "Prefer unencrypted peer connections",
68      "et", 0, NULL                  },
69    { 'f', "files",                "List the current torrent's files",
70      "f",  0, NULL                  },
71    { 'g', "get",                  "Mark files for download",
72      "g",  1, "<files>"             },
73    { 'G', "no-get",               "Mark files for not downloading",
74      "G",  1, "<files>"             },
75    { 'i', "info",                 "Show details of the current torrent(s)",
76      "i",  0, NULL                  },
77    { 'l', "list",                 "List all torrents",
78      "l",  0, NULL                  },
79    { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP",
80      "m",  0, NULL                  },
81    { 'M', "no-portmap",           "Disable portmapping",
82      "M",  0, NULL                  },
83    { 'n', "auth",                 "Set authentication info",
84      "n",  1, "<username:password>" },
85    { 'p', "port",
86      "Port for incoming peers (Default: " TR_DEFAULT_PORT_STR ")",
87      "p", 1, "<port>" },
88    { 900, "priority-high",        "Set the files' priorities as high",
89      "ph", 1, "<files>"             },
90    { 901, "priority-normal",      "Set the files' priorities as normal",
91      "pn", 1, "<files>"             },
92    { 902, "priority-low",         "Set the files' priorities as low",
93      "pl", 1, "<files>"             },
94    { 'r', "remove",               "Remove the current torrent(s)",
95      "r",  0, NULL                  },
96    { 's', "start",                "Start the current torrent(s)",
97      "s",  0, NULL                  },
98    { 'S', "stop",                 "Stop the current torrent(s)",
99      "S",  0, NULL                  },
100    { 't', "torrent",              "Set the current torrent(s)",
101      "t",  1, "<torrent>"           },
102    { 'u', "uplimit",              "Set the maximum upload speed in KB/s",
103      "u",  1, "<speed>"             },
104    { 'U', "no-uplimit",           "Don't limit the upload speed",
105      "U",  0, NULL                  },
106    { 'v', "verify",               "Verify the current torrent(s)",
107      "v",  0, NULL                  },
108    { 'w', "download-dir",         "Set the default download folder",
109      "w",  1, "<path>"              },
110    { 'x', "pex",                  "Enable peer exchange (PEX)",
111      "x",  0, NULL                  },
112    { 'X', "no-pex",               "Disable peer exchange (PEX)",
113      "X",  0, NULL                  },
114    { 'z', "peers",                "List the current torrent's peers",
115      "z",  0, NULL                  },
116    {   0, NULL,                   NULL,
117        NULL, 0, NULL                  }
118};
119
120static void
121showUsage( void )
122{
123    tr_getopt_usage( MY_NAME, getUsage( ), opts );
124    exit( 0 );
125}
126
127static int
128numarg( const char * arg )
129{
130    char *     end = NULL;
131    const long num = strtol( arg, &end, 10 );
132
133    if( *end )
134    {
135        fprintf( stderr, "Not a number: \"%s\"\n", arg );
136        showUsage( );
137    }
138    return num;
139}
140
141static char * reqs[256]; /* arbitrary max */
142static int    reqCount = 0;
143static int    debug = 0;
144static char * auth = NULL;
145
146static char*
147absolutify( const char * path )
148{
149    char * buf;
150
151    if( *path == '/' )
152        buf = tr_strdup( path );
153    else {
154        char * cwd = tr_getcwd( );
155        buf = tr_buildPath( cwd, path, NULL );
156        tr_free( cwd );
157    }
158
159    return buf;
160}
161
162static char*
163getEncodedMetainfo( const char * filename )
164{
165    size_t    len = 0;
166    char *    b64 = NULL;
167    uint8_t * buf = tr_loadFile( filename, &len );
168
169    if( buf )
170    {
171        b64 = tr_base64_encode( buf, len, NULL );
172        tr_free( buf );
173    }
174    return b64;
175}
176
177static void
178addIdArg( tr_benc *    args,
179          const char * id )
180{
181    if( !*id )
182    {
183        fprintf(
184            stderr,
185            "No torrent specified!  Please use the -t option first.\n" );
186        id = "-1"; /* no torrent will have this ID, so should be a no-op */
187    }
188    if( strcmp( id, "all" ) )
189    {
190        tr_rpc_parse_list_str( tr_bencDictAdd( args,
191                                               "ids" ), id, strlen( id ) );
192    }
193}
194
195static void
196addFiles( tr_benc *    args,
197          const char * key,
198          const char * arg )
199{
200    tr_benc * files = tr_bencDictAddList( args, key, 100 );
201
202    if( !*arg )
203    {
204        fprintf( stderr, "No files specified!\n" );
205        arg = "-1"; /* no file will have this index, so should be a no-op */
206    }
207    if( strcmp( arg, "all" ) )
208    {
209        const char * walk = arg;
210        while( *walk )
211        {
212            char *        p;
213            unsigned long l;
214            errno = 0;
215            l = strtol( walk, &p, 10 );
216            if( errno )
217                break;
218            tr_bencListAddInt( files, l - 1 );
219            if( *p != ',' )
220                break;
221            walk = p + 1;
222        }
223    }
224}
225
226#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
227
228static const char * files_keys[] = {
229    "files", "name", "priorities", "wanted"
230};
231
232static const char * details_keys[] = {
233    "activityDate",     "addedDate",              "announceResponse",
234    "announceURL",
235    "comment",        "corruptEver",        "creator",
236    "dateCreated",     "doneDate",
237    "downloadedEver", "errorString",        "eta",
238    "hashString",      "haveUnchecked",
239    "haveValid",      "id",                 "isPrivate",
240    "lastAnnounceTime", "lastScrapeTime",
241    "leechers",       "leftUntilDone",      "name",
242    "nextAnnounceTime", "nextScrapeTime",
243    "peersConnected", "peersGettingFromUs", "peersSendingToUs",
244    "pieceCount",     "pieceSize",          "rateDownload",
245    "rateUpload",      "recheckProgress",
246    "scrapeResponse", "seeders",
247    "sizeWhenDone",    "startDate",
248    "status",         "timesCompleted",     "totalSize",
249    "uploadedEver",
250    "webseeds",       "webseedsSendingToUs"
251};
252
253static const char * list_keys[] = {
254    "downloadedEver", "eta",              "id",
255    "leftUntilDone",
256    "name",
257    "peersGettingFromUs", "peersSendingToUs",
258    "rateDownload",
259    "rateUpload", "sizeWhenDone", "status", "uploadedEver"
260};
261
262static void
263readargs( int           argc,
264          const char ** argv )
265{
266    int          c;
267    int          addingTorrents = 0;
268    const char * optarg;
269    char         id[4096];
270
271    *id = '\0';
272
273    while( ( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg ) ) )
274    {
275        int     i, n;
276        int     addArg = TRUE;
277        tr_benc top, *args, *fields;
278        tr_bencInitDict( &top, 3 );
279        args = tr_bencDictAddDict( &top, "arguments", 0 );
280
281        switch( c )
282        {
283            case TR_OPT_UNK:
284                if( addingTorrents )
285                {
286                    char * tmp = getEncodedMetainfo( optarg );
287                    if( tmp )
288                    {
289                        tr_bencDictAddStr( &top, "method", "torrent-add" );
290                        tr_bencDictAddStr( args, "metainfo", tmp );
291                        tr_free( tmp );
292                    }
293                    else
294                    {
295                        fprintf( stderr, "Couldn't add file: %s\n", optarg );
296                        addArg = FALSE;
297                    }
298                }
299                else
300                {
301                    fprintf( stderr, "Unknown option: %s\n", optarg );
302                    addArg = FALSE;
303                }
304                break;
305
306            case 'a':
307                addingTorrents = 1;
308                addArg = FALSE;
309                break;
310
311            case 'b':
312                debug = 1;
313                addArg = FALSE;
314                break;
315
316            case 'd':
317                tr_bencDictAddStr( &top, "method", "session-set" );
318                tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
319                tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
320                break;
321
322            case 'D':
323                tr_bencDictAddStr( &top, "method", "session-set" );
324                tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
325                break;
326
327            case 'f':
328                tr_bencDictAddStr( &top, "method", "torrent-get" );
329                tr_bencDictAddInt( &top, "tag", TAG_FILES );
330                addIdArg( args, id );
331                n = TR_N_ELEMENTS( files_keys );
332                fields = tr_bencDictAddList( args, "fields", n );
333                for( i = 0; i < n; ++i )
334                    tr_bencListAddStr( fields, files_keys[i] );
335                break;
336
337            case 'g':
338                tr_bencDictAddStr( &top, "method", "torrent-set" );
339                addIdArg( args, id );
340                addFiles( args, "files-wanted", optarg );
341                break;
342
343            case 'G':
344                tr_bencDictAddStr( &top, "method", "torrent-set" );
345                addIdArg( args, id );
346                addFiles( args, "files-unwanted", optarg );
347                break;
348
349            case 'i':
350                tr_bencDictAddStr( &top, "method", "torrent-get" );
351                tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
352                addIdArg( args, id );
353                n = TR_N_ELEMENTS( details_keys );
354                fields = tr_bencDictAddList( args, "fields", n );
355                for( i = 0; i < n; ++i )
356                    tr_bencListAddStr( fields, details_keys[i] );
357                break;
358
359            case 'l':
360                tr_bencDictAddStr( &top, "method", "torrent-get" );
361                tr_bencDictAddInt( &top, "tag", TAG_LIST );
362                n = TR_N_ELEMENTS( list_keys );
363                fields = tr_bencDictAddList( args, "fields", n );
364                for( i = 0; i < n; ++i )
365                    tr_bencListAddStr( fields, list_keys[i] );
366                break;
367
368            case 'm':
369                tr_bencDictAddStr( &top, "method", "session-set" );
370                tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
371                break;
372
373            case 'M':
374                tr_bencDictAddStr( &top, "method", "session-set" );
375                tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
376                break;
377
378            case 'n':
379                auth = tr_strdup( optarg );
380                addArg = FALSE;
381                break;
382
383            case 'p':
384                tr_bencDictAddStr( &top, "method", "session-set" );
385                tr_bencDictAddInt( args, "port", numarg( optarg ) );
386                break;
387
388            case 'r':
389                tr_bencDictAddStr( &top, "method", "torrent-remove" );
390                addIdArg( args, id );
391                break;
392
393            case 's':
394                tr_bencDictAddStr( &top, "method", "torrent-start" );
395                addIdArg( args, id );
396                break;
397
398            case 'S':
399                tr_bencDictAddStr( &top, "method", "torrent-stop" );
400                addIdArg( args, id );
401                break;
402
403            case 't':
404                tr_strlcpy( id, optarg, sizeof( id ) );
405                addArg = FALSE;
406                break;
407
408            case 'u':
409                tr_bencDictAddStr( &top, "method", "session-set" );
410                tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
411                tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
412                break;
413
414            case 'U':
415                tr_bencDictAddStr( &top, "method", "session-set" );
416                tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
417                break;
418
419            case 'v':
420                tr_bencDictAddStr( &top, "method", "torrent-verify" );
421                addIdArg( args, id );
422                break;
423
424            case 'w': {
425                char * path = absolutify( optarg );
426                tr_bencDictAddStr( &top, "method", "session-set" );
427                tr_bencDictAddStr( args, "download-dir", path );
428                tr_free( path );
429                break;
430            }
431
432            case 'x':
433                tr_bencDictAddStr( &top, "method", "session-set" );
434                tr_bencDictAddInt( args, "pex-allowed", 1 );
435                break;
436
437            case 'X':
438                tr_bencDictAddStr( &top, "method", "session-set" );
439                tr_bencDictAddInt( args, "pex-allowed", 0 );
440                break;
441
442            case 'z':
443                tr_bencDictAddStr( &top, "method", "torrent-get" );
444                tr_bencDictAddInt( &top, "tag", TAG_PEERS );
445                addIdArg( args, id );
446                fields = tr_bencDictAddList( args, "fields", 1 );
447                tr_bencListAddStr( fields, "peers" );
448                break;
449
450            case 900:
451                tr_bencDictAddStr( &top, "method", "torrent-set" );
452                addIdArg( args, id );
453                addFiles( args, "priority-high", optarg );
454                break;
455
456            case 901:
457                tr_bencDictAddStr( &top, "method", "torrent-set" );
458                addIdArg( args, id );
459                addFiles( args, "priority-normal", optarg );
460                break;
461
462            case 902:
463                tr_bencDictAddStr( &top, "method", "torrent-set" );
464                addIdArg( args, id );
465                addFiles( args, "priority-low", optarg );
466                break;
467
468            case 910:
469                tr_bencDictAddStr( &top, "method", "session-set" );
470                tr_bencDictAddStr( args, "encryption", "required" );
471                break;
472
473            case 911:
474                tr_bencDictAddStr( &top, "method", "session-set" );
475                tr_bencDictAddStr( args, "encryption", "preferred" );
476                break;
477
478            case 912:
479                tr_bencDictAddStr( &top, "method", "session-set" );
480                tr_bencDictAddStr( args, "encryption", "tolerated" );
481                break;
482
483            case TR_OPT_ERR:
484                fprintf( stderr, "invalid option\n" );
485                showUsage( );
486                break;
487
488            default:
489                fprintf( stderr, "got opt [%d]\n", (int)c );
490                showUsage( );
491                break;
492        }
493
494        if( addArg )
495            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
496        tr_bencFree( &top );
497    }
498}
499
500/* [host:port] or [host] or [port] */
501static void
502getHostAndPort( int *   argc,
503                char ** argv,
504                char ** host,
505                int *   port )
506{
507    if( *argv[1] != '-' )
508    {
509        int          i;
510        const char * s = argv[1];
511        const char * delim = strchr( s, ':' );
512        if( delim )   /* user passed in both host and port */
513        {
514            *host = tr_strndup( s, delim - s );
515            *port = atoi( delim + 1 );
516        }
517        else
518        {
519            char *    end;
520            const int i = strtol( s, &end, 10 );
521            if( !*end ) /* user passed in a port */
522                *port = i;
523            else /* user passed in a host */
524                *host = tr_strdup( s );
525        }
526
527        *argc -= 1;
528        for( i = 1; i < *argc; ++i )
529            argv[i] = argv[i + 1];
530    }
531}
532
533static size_t
534writeFunc( void * ptr,
535           size_t size,
536           size_t nmemb,
537           void * buf )
538{
539    const size_t byteCount = size * nmemb;
540
541    evbuffer_add( buf, ptr, byteCount );
542    return byteCount;
543}
544
545static void
546etaToString( char *  buf,
547             size_t  buflen,
548             int64_t eta )
549{
550    if( eta < 0 ) tr_snprintf( buf, buflen, "Unknown" );
551    else if( eta < 60 ) tr_snprintf( buf, buflen, "%" PRId64 "sec", eta );
552    else if( eta <
553            ( 60 * 60 ) ) tr_snprintf( buf, buflen, "%" PRId64 " min",
554                                       eta / 60 );
555    else if( eta <
556            ( 60 * 60 * 24 ) ) tr_snprintf( buf, buflen, "%" PRId64 " hrs",
557                                           eta / ( 60 * 60 ) );
558    else tr_snprintf( buf, buflen, "%" PRId64 " days", eta / ( 60 * 60 * 24 ) );
559}
560
561#define KILOBYTE_FACTOR 1024.0
562#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
563#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
564
565static char*
566strlratio( char * buf,
567           double numerator,
568           double denominator,
569           size_t buflen )
570{
571    if( denominator )
572    {
573        const double ratio = numerator / denominator;
574        if( ratio < 10.0 )
575            tr_snprintf( buf, buflen, "%'.2f", ratio );
576        else if( ratio < 100.0 )
577            tr_snprintf( buf, buflen, "%'.1f", ratio );
578        else
579            tr_snprintf( buf, buflen, "%'.0f", ratio );
580    }
581    else if( numerator )
582        tr_strlcpy( buf, "Infinity", buflen );
583    else
584        tr_strlcpy( buf, "None", buflen );
585    return buf;
586}
587
588static char*
589strlsize( char *  buf,
590          int64_t size,
591          size_t  buflen )
592{
593    if( !size )
594        tr_strlcpy( buf, "None", buflen );
595    else if( size < (int64_t)KILOBYTE_FACTOR )
596        tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
597    else
598    {
599        double displayed_size;
600        if( size < (int64_t)MEGABYTE_FACTOR )
601        {
602            displayed_size = (double) size / KILOBYTE_FACTOR;
603            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
604        }
605        else if( size < (int64_t)GIGABYTE_FACTOR )
606        {
607            displayed_size = (double) size / MEGABYTE_FACTOR;
608            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
609        }
610        else
611        {
612            displayed_size = (double) size / GIGABYTE_FACTOR;
613            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
614        }
615    }
616    return buf;
617}
618
619static char*
620getStatusString( tr_benc * t, char * buf, size_t buflen )
621{
622    int64_t status;
623
624    if( !tr_bencDictFindInt( t, "status", &status ) )
625    {
626        *buf = '\0';
627    }
628    else switch( status )
629    {
630        case TR_STATUS_STOPPED:
631            tr_strlcpy( buf, "Stopped", buflen );
632            break;
633
634        case TR_STATUS_CHECK_WAIT:
635        case TR_STATUS_CHECK: {
636            const char * str = status == TR_STATUS_CHECK_WAIT
637                             ? "Will Verify"
638                             : "Verifying";
639            double percent;
640            if( tr_bencDictFindDouble( t, "recheckProgress", &percent ) )
641                tr_snprintf( buf, buflen, "%s (%.0f%%)", str, percent*100.0 );
642            else
643                tr_strlcpy( buf, str, buflen );
644
645            break;
646        }
647
648        case TR_STATUS_DOWNLOAD:
649        case TR_STATUS_SEED: {
650            int64_t fromUs = 0;
651            int64_t toUs = 0;
652            tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
653            tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
654            if( fromUs && toUs )
655                tr_strlcpy( buf, "Up & Down", buflen );
656            else if( toUs )
657                tr_strlcpy( buf, "Downloading", buflen );
658            else if( fromUs ) {
659                int64_t leftUntilDone = 0;
660                tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
661                if( leftUntilDone > 0 )
662                    tr_strlcpy( buf, "Uploading", buflen );
663                else
664                    tr_strlcpy( buf, "Seeding", buflen );
665            } else
666                tr_strlcpy( buf, "Idle", buflen );
667            break;
668        }
669    }
670
671    return buf;
672}
673
674static void
675printDetails( tr_benc * top )
676{
677    tr_benc *args, *torrents;
678
679    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
680      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
681    {
682        int ti, tCount;
683        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
684             ++ti )
685        {
686            tr_benc *    t = tr_bencListChild( torrents, ti );
687            tr_benc *    l;
688            const char * str;
689            char         buf[512];
690            char         buf2[512];
691            int64_t      i, j, k;
692
693            printf( "NAME\n" );
694            if( tr_bencDictFindInt( t, "id", &i ) )
695                printf( "  Id: %" PRId64 "\n", i );
696            if( tr_bencDictFindStr( t, "name", &str ) )
697                printf( "  Name: %s\n", str );
698            if( tr_bencDictFindStr( t, "hashString", &str ) )
699                printf( "  Hash: %s\n", str );
700            printf( "\n" );
701
702            printf( "TRANSFER\n" );
703            getStatusString( t, buf, sizeof( buf ) );
704            printf( "  State: %s\n", buf );
705
706            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
707              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
708            {
709                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
710                printf( "  Percent Done: %s%%\n", buf );
711            }
712
713            if( tr_bencDictFindInt( t, "eta", &i ) )
714            {
715                etaToString( buf, sizeof( buf ), i );
716                printf( "  ETA: %s\n", buf );
717            }
718            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
719                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
720            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
721                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
722            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
723              && tr_bencDictFindInt( t, "haveValid", &j ) )
724            {
725                strlsize( buf, i + j, sizeof( buf ) );
726                strlsize( buf2, j, sizeof( buf2 ) );
727                printf( "  Have: %s (%s verified)\n", buf, buf2 );
728            }
729
730            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
731              && tr_bencDictFindInt( t, "totalSize", &j ) )
732            {
733                strlsize( buf, j, sizeof( buf ) );
734                strlsize( buf2, i, sizeof( buf2 ) );
735                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
736            }
737            if( tr_bencDictFindInt( t, "downloadedEver", &i )
738              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
739            {
740                strlsize( buf, i, sizeof( buf ) );
741                printf( "  Downloaded: %s\n", buf );
742                strlsize( buf, j, sizeof( buf ) );
743                printf( "  Uploaded: %s\n", buf );
744                strlratio( buf, j, i, sizeof( buf ) );
745                printf( "  Ratio: %s\n", buf );
746            }
747            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
748            {
749                strlsize( buf, i, sizeof( buf ) );
750                printf( "  Corrupt DL: %s\n", buf );
751            }
752            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
753                printf( "  Error: %s\n", str );
754
755            if( tr_bencDictFindInt( t, "peersConnected", &i )
756              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
757              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
758            {
759                printf(
760                    "  Peers: "
761                    "connected to %" PRId64 ", "
762                                            "uploading to %" PRId64
763                    ", "
764                    "downloading from %"
765                    PRId64 "\n",
766                    i, j, k );
767            }
768
769            if( tr_bencDictFindList( t, "webseeds", &l )
770              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
771            {
772                const int64_t n = tr_bencListSize( l );
773                if( n > 0 )
774                    printf(
775                        "  Web Seeds: downloading from %" PRId64 " of %"
776                        PRId64
777                        " web seeds\n", i, n );
778            }
779            printf( "\n" );
780
781            printf( "HISTORY\n" );
782            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
783            {
784                const time_t tt = i;
785                printf( "  Date added:      %s", ctime( &tt ) );
786            }
787            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
788            {
789                const time_t tt = i;
790                printf( "  Date finished:   %s", ctime( &tt ) );
791            }
792            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
793            {
794                const time_t tt = i;
795                printf( "  Date started:    %s", ctime( &tt ) );
796            }
797            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
798            {
799                const time_t tt = i;
800                printf( "  Latest activity: %s", ctime( &tt ) );
801            }
802            printf( "\n" );
803
804            printf( "TRACKER\n" );
805            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) && i )
806            {
807                const time_t tt = i;
808                printf( "  Latest announce: %s", ctime( &tt ) );
809            }
810            if( tr_bencDictFindStr( t, "announceURL", &str ) )
811                printf( "  Announce URL: %s\n", str );
812            if( tr_bencDictFindStr( t, "announceResponse",
813                                    &str ) && str && *str )
814                printf( "  Announce response: %s\n", str );
815            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) && i )
816            {
817                const time_t tt = i;
818                printf( "  Next announce:   %s", ctime( &tt ) );
819            }
820            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) && i )
821            {
822                const time_t tt = i;
823                printf( "  Latest scrape:   %s", ctime( &tt ) );
824            }
825            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
826                printf( "  Scrape response: %s\n", str );
827            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) && i )
828            {
829                const time_t tt = i;
830                printf( "  Next scrape:     %s", ctime( &tt ) );
831            }
832            if( tr_bencDictFindInt( t, "seeders", &i )
833              && tr_bencDictFindInt( t, "leechers", &j ) )
834                printf(
835                    "  Tracker knows of %" PRId64 " seeders and %" PRId64
836                    " leechers\n", i, j );
837            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
838                printf(
839                    "  Tracker has seen %" PRId64
840                    " clients complete this torrent\n", i );
841            printf( "\n" );
842
843            printf( "ORIGINS\n" );
844            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
845            {
846                const time_t tt = i;
847                printf( "  Date created: %s", ctime( &tt ) );
848            }
849            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
850                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
851            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
852                printf( "  Comment: %s\n", str );
853            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
854                printf( "  Creator: %s\n", str );
855            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
856                printf( "  Piece Count: %" PRId64 "\n", i );
857            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
858                printf( "  Piece Size: %" PRId64 "\n", i );
859        }
860    }
861}
862
863static void
864printFileList( tr_benc * top )
865{
866    tr_benc *args, *torrents;
867
868    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
869      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
870    {
871        int i, in;
872        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
873        {
874            tr_benc *    d = tr_bencListChild( torrents, i );
875            tr_benc *    files, *priorities, *wanteds;
876            const char * name;
877            if( tr_bencDictFindStr( d, "name", &name )
878              && tr_bencDictFindList( d, "files", &files )
879              && tr_bencDictFindList( d, "priorities", &priorities )
880              && tr_bencDictFindList( d, "wanted", &wanteds ) )
881            {
882                int j = 0, jn = tr_bencListSize( files );
883                printf( "%s (%d files):\n", name, jn );
884                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
885                        "Priority", "Get", "Size",
886                        "Name" );
887                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
888                {
889                    int64_t      have;
890                    int64_t      length;
891                    int64_t      priority;
892                    int64_t      wanted;
893                    const char * filename;
894                    tr_benc *    file = tr_bencListChild( files, j );
895                    if( tr_bencDictFindInt( file, "length", &length )
896                      && tr_bencDictFindStr( file, "name", &filename )
897                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
898                      && tr_bencGetInt( tr_bencListChild( priorities,
899                                                          j ), &priority )
900                      && tr_bencGetInt( tr_bencListChild( wanteds,
901                                                          j ), &wanted ) )
902                    {
903                        char         sizestr[64];
904                        double       percent = (double)have / length;
905                        const char * pristr;
906                        strlsize( sizestr, length, sizeof( sizestr ) );
907                        switch( priority )
908                        {
909                            case TR_PRI_LOW:
910                                pristr = "Low"; break;
911
912                            case TR_PRI_HIGH:
913                                pristr = "High"; break;
914
915                            default:
916                                pristr = "Normal"; break;
917                        }
918                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
919                                ( j + 1 ),
920                                ( 100.0 * percent ),
921                                pristr,
922                                ( wanted ? "Yes" : "No" ),
923                                sizestr,
924                                filename );
925                    }
926                }
927            }
928        }
929    }
930}
931
932static void
933printPeersImpl( tr_benc * peers )
934{
935    int i, n;
936    printf( "%-20s  %-12s  %-6s  %-6s  %s\n",
937            "Address", "Flags", "Down", "Up", "Client" );
938    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
939    {
940        const char * address, * client, * flagstr;
941        int64_t      rateToClient, rateToPeer;
942        tr_benc *    d = tr_bencListChild( peers, i );
943        if( tr_bencDictFindStr( d, "address", &address )
944          && tr_bencDictFindStr( d, "clientName", &client )
945          && tr_bencDictFindStr( d, "flagStr", &flagstr )
946          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
947          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
948        {
949            printf( "%-20s  %-12s  %6.1f  %6.1f  %s\n",
950                    address, flagstr,
951                    rateToClient / 1024.0,
952                    rateToPeer / 1024.0,
953                    client );
954        }
955    }
956}
957
958static void
959printPeers( tr_benc * top )
960{
961    tr_benc *args, *torrents;
962
963    if( tr_bencDictFindDict( top, "arguments", &args )
964      && tr_bencDictFindList( args, "torrents", &torrents ) )
965    {
966        int i, n;
967        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
968        {
969            tr_benc * peers;
970            tr_benc * torrent = tr_bencListChild( torrents, i );
971            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
972                printPeersImpl( peers );
973                if( i+1<n )
974                    printf( "\n" );
975            }
976        }
977    }
978}
979
980static void
981printTorrentList( tr_benc * top )
982{
983    tr_benc *args, *list;
984
985    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
986      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
987    {
988        int i, n;
989        printf( "%-4s  %-4s  %-8s  %-6s  %-6s  %-5s  %-11s  %s\n",
990                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status",
991                "Name" );
992        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
993        {
994            int64_t     id, eta, status, up, down;
995            int64_t     sizeWhenDone, leftUntilDone;
996            int64_t     upEver, downEver;
997            const char *name;
998            tr_benc *   d = tr_bencListChild( list, i );
999            if( tr_bencDictFindInt( d, "downloadedEver", &downEver )
1000              && tr_bencDictFindInt( d, "eta", &eta )
1001              && tr_bencDictFindInt( d, "id", &id )
1002              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1003              && tr_bencDictFindStr( d, "name", &name )
1004              && tr_bencDictFindInt( d, "rateDownload", &down )
1005              && tr_bencDictFindInt( d, "rateUpload", &up )
1006              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1007              && tr_bencDictFindInt( d, "status", &status )
1008              && tr_bencDictFindInt( d, "uploadedEver", &upEver ) )
1009            {
1010                char etaStr[16];
1011                char statusStr[64];
1012                if( leftUntilDone )
1013                    etaToString( etaStr, sizeof( etaStr ), eta );
1014                else
1015                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1016                printf(
1017                    "%4d  %3d%%  %-8s  %6.1f  %6.1f  %5.1f  %-11s  %s\n",
1018                    (int)id,
1019                    (int)( 100.0 *
1020                           ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ),
1021                    etaStr,
1022                    up / 1024.0,
1023                    down / 1024.0,
1024                    (double)( downEver ? ( (double)upEver /
1025                                          downEver ) : 0.0 ),
1026                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1027                    name );
1028            }
1029        }
1030    }
1031}
1032
1033static void
1034processResponse( const char * host,
1035                 int          port,
1036                 const void * response,
1037                 size_t       len )
1038{
1039    tr_benc top;
1040
1041    if( debug )
1042        fprintf( stderr, "got response: [%*.*s]\n",
1043                 (int)len, (int)len, (const char*) response );
1044
1045    if( tr_jsonParse( response, len, &top, NULL ) )
1046        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1047                 (int)len, (char*)response );
1048    else
1049    {
1050        int64_t      tag = -1;
1051        const char * str;
1052        tr_bencDictFindInt( &top, "tag", &tag );
1053
1054        switch( tag )
1055        {
1056            case TAG_FILES:
1057                printFileList( &top ); break;
1058
1059            case TAG_DETAILS:
1060                printDetails( &top ); break;
1061
1062            case TAG_LIST:
1063                printTorrentList( &top ); break;
1064
1065            case TAG_PEERS:
1066                printPeers( &top ); break;
1067
1068            default:
1069                if( tr_bencDictFindStr( &top, "result", &str ) )
1070                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1071        }
1072
1073        tr_bencFree( &top );
1074    }
1075}
1076
1077static void
1078processRequests( const char *  host,
1079                 int           port,
1080                 const char ** reqs,
1081                 int           reqCount )
1082{
1083    int               i;
1084    CURL *            curl;
1085    struct evbuffer * buf = evbuffer_new( );
1086    char *            url = tr_strdup_printf(
1087        "http://%s:%d/transmission/rpc", host, port );
1088
1089    curl = curl_easy_init( );
1090    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1091#ifdef HAVE_LIBZ
1092    curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
1093#endif
1094    curl_easy_setopt( curl, CURLOPT_USERAGENT,
1095                      MY_NAME "/" LONG_VERSION_STRING );
1096    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1097    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
1098    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1099    curl_easy_setopt( curl, CURLOPT_URL, url );
1100    if( auth )
1101    {
1102        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1103        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1104    }
1105
1106    for( i = 0; i < reqCount; ++i )
1107    {
1108        CURLcode res;
1109        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1110        if( debug )
1111            fprintf( stderr, "posting [%s]\n", reqs[i] );
1112        if( ( res = curl_easy_perform( curl ) ) )
1113            tr_nerr( MY_NAME, "(%s:%d) %s", host, port,
1114                    curl_easy_strerror( res ) );
1115        else
1116            processResponse( host, port, EVBUFFER_DATA(
1117                                buf ), EVBUFFER_LENGTH( buf ) );
1118
1119        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1120    }
1121
1122    /* cleanup */
1123    tr_free( url );
1124    evbuffer_free( buf );
1125    curl_easy_cleanup( curl );
1126}
1127
1128int
1129main( int     argc,
1130      char ** argv )
1131{
1132    int    i;
1133    int    port = DEFAULT_PORT;
1134    char * host = NULL;
1135
1136    if( argc < 2 )
1137        showUsage( );
1138
1139    getHostAndPort( &argc, argv, &host, &port );
1140    if( host == NULL )
1141        host = tr_strdup( DEFAULT_HOST );
1142
1143    readargs( argc, (const char**)argv );
1144    if( reqCount )
1145        processRequests( host, port, (const char**)reqs, reqCount );
1146    else
1147        showUsage( );
1148
1149    for( i = 0; i < reqCount; ++i )
1150        tr_free( reqs[i] );
1151
1152    tr_free( host );
1153    return 0;
1154}
1155
Note: See TracBrowser for help on using the repository browser.