source: trunk/daemon/remote.c @ 7003

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

(daemon) make getStatusStr() pass the smell test a little better

  • Property svn:keywords set to Date Rev Author Id
File size: 37.5 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: remote.c 7003 2008-11-01 06:22:14Z 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",            "sizeWhenDone",
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                fields = tr_bencDictAddList( args, "fields", 1 );
446                tr_bencListAddStr( fields, "peers" );
447                break;
448
449            case 900:
450                tr_bencDictAddStr( &top, "method", "torrent-set" );
451                addIdArg( args, id );
452                addFiles( args, "priority-high", optarg );
453                break;
454
455            case 901:
456                tr_bencDictAddStr( &top, "method", "torrent-set" );
457                addIdArg( args, id );
458                addFiles( args, "priority-normal", optarg );
459                break;
460
461            case 902:
462                tr_bencDictAddStr( &top, "method", "torrent-set" );
463                addIdArg( args, id );
464                addFiles( args, "priority-low", optarg );
465                break;
466
467            case 910:
468                tr_bencDictAddStr( &top, "method", "session-set" );
469                tr_bencDictAddStr( args, "encryption", "required" );
470                break;
471
472            case 911:
473                tr_bencDictAddStr( &top, "method", "session-set" );
474                tr_bencDictAddStr( args, "encryption", "preferred" );
475                break;
476
477            case 912:
478                tr_bencDictAddStr( &top, "method", "session-set" );
479                tr_bencDictAddStr( args, "encryption", "tolerated" );
480                break;
481
482            case TR_OPT_ERR:
483                fprintf( stderr, "invalid option\n" );
484                showUsage( );
485                break;
486
487            default:
488                fprintf( stderr, "got opt [%d]\n", (int)c );
489                showUsage( );
490                break;
491        }
492
493        if( addArg )
494            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
495        tr_bencFree( &top );
496    }
497}
498
499/* [host:port] or [host] or [port] */
500static void
501getHostAndPort( int *   argc,
502                char ** argv,
503                char ** host,
504                int *   port )
505{
506    if( *argv[1] != '-' )
507    {
508        int          i;
509        const char * s = argv[1];
510        const char * delim = strchr( s, ':' );
511        if( delim )   /* user passed in both host and port */
512        {
513            *host = tr_strndup( s, delim - s );
514            *port = atoi( delim + 1 );
515        }
516        else
517        {
518            char *    end;
519            const int i = strtol( s, &end, 10 );
520            if( !*end ) /* user passed in a port */
521                *port = i;
522            else /* user passed in a host */
523                *host = tr_strdup( s );
524        }
525
526        *argc -= 1;
527        for( i = 1; i < *argc; ++i )
528            argv[i] = argv[i + 1];
529    }
530}
531
532static size_t
533writeFunc( void * ptr,
534           size_t size,
535           size_t nmemb,
536           void * buf )
537{
538    const size_t byteCount = size * nmemb;
539
540    evbuffer_add( buf, ptr, byteCount );
541    return byteCount;
542}
543
544static void
545etaToString( char *  buf,
546             size_t  buflen,
547             int64_t eta )
548{
549    if( eta < 0 ) tr_snprintf( buf, buflen, "Unknown" );
550    else if( eta < 60 ) tr_snprintf( buf, buflen, "%" PRId64 "sec", eta );
551    else if( eta <
552            ( 60 * 60 ) ) tr_snprintf( buf, buflen, "%" PRId64 " min",
553                                       eta / 60 );
554    else if( eta <
555            ( 60 * 60 * 24 ) ) tr_snprintf( buf, buflen, "%" PRId64 " hrs",
556                                           eta / ( 60 * 60 ) );
557    else tr_snprintf( buf, buflen, "%" PRId64 " days", eta / ( 60 * 60 * 24 ) );
558}
559
560#define KILOBYTE_FACTOR 1024.0
561#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
562#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
563
564static char*
565strlratio( char * buf,
566           double numerator,
567           double denominator,
568           size_t buflen )
569{
570    if( denominator )
571    {
572        const double ratio = numerator / denominator;
573        if( ratio < 10.0 )
574            tr_snprintf( buf, buflen, "%'.2f", ratio );
575        else if( ratio < 100.0 )
576            tr_snprintf( buf, buflen, "%'.1f", ratio );
577        else
578            tr_snprintf( buf, buflen, "%'.0f", ratio );
579    }
580    else if( numerator )
581        tr_strlcpy( buf, "Infinity", buflen );
582    else
583        tr_strlcpy( buf, "None", buflen );
584    return buf;
585}
586
587static char*
588strlsize( char *  buf,
589          int64_t size,
590          size_t  buflen )
591{
592    if( !size )
593        tr_strlcpy( buf, "None", buflen );
594    else if( size < (int64_t)KILOBYTE_FACTOR )
595        tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
596    else
597    {
598        double displayed_size;
599        if( size < (int64_t)MEGABYTE_FACTOR )
600        {
601            displayed_size = (double) size / KILOBYTE_FACTOR;
602            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
603        }
604        else if( size < (int64_t)GIGABYTE_FACTOR )
605        {
606            displayed_size = (double) size / MEGABYTE_FACTOR;
607            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
608        }
609        else
610        {
611            displayed_size = (double) size / GIGABYTE_FACTOR;
612            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
613        }
614    }
615    return buf;
616}
617
618static char*
619getStatusString( tr_benc * t, char * buf, size_t buflen )
620{
621    int64_t status;
622
623    if( !tr_bencDictFindInt( t, "status", &status ) )
624    {
625        *buf = '\0';
626    }
627    else switch( status )
628    {
629        case TR_STATUS_STOPPED:
630            tr_strlcpy( buf, "Stopped", buflen );
631            break;
632
633        case TR_STATUS_CHECK_WAIT:
634        case TR_STATUS_CHECK: {
635            const char * str = status == TR_STATUS_CHECK_WAIT
636                             ? "Will Verify"
637                             : "Verifying";
638            double percent;
639            if( tr_bencDictFindDouble( t, "recheckProgress", &percent ) )
640                tr_snprintf( buf, buflen, "%s (%.0f%%)", str, percent*100.0 );
641            else
642                tr_strlcpy( buf, str, buflen );
643
644            break;
645        }
646
647        case TR_STATUS_DOWNLOAD:
648        case TR_STATUS_SEED: {
649            int64_t fromUs = 0;
650            int64_t toUs = 0;
651            tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
652            tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
653            if( fromUs && toUs )
654                tr_strlcpy( buf, "Up & Down", buflen );
655            else if( fromUs )
656                tr_strlcpy( buf, "Seeding", buflen );
657            else if( toUs )
658                tr_strlcpy( buf, "Downloading", buflen );
659            else
660                tr_strlcpy( buf, "Idle", buflen );
661            break;
662        }
663    }
664
665    return buf;
666}
667
668static void
669printDetails( tr_benc * top )
670{
671    tr_benc *args, *torrents;
672
673    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
674      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
675    {
676        int ti, tCount;
677        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
678             ++ti )
679        {
680            tr_benc *    t = tr_bencListChild( torrents, ti );
681            tr_benc *    l;
682            const char * str;
683            char         buf[512];
684            char         buf2[512];
685            int64_t      i, j, k;
686
687            printf( "NAME\n" );
688            if( tr_bencDictFindInt( t, "id", &i ) )
689                printf( "  Id: %" PRId64 "\n", i );
690            if( tr_bencDictFindStr( t, "name", &str ) )
691                printf( "  Name: %s\n", str );
692            if( tr_bencDictFindStr( t, "hashString", &str ) )
693                printf( "  Hash: %s\n", str );
694            printf( "\n" );
695
696            printf( "TRANSFER\n" );
697            getStatusString( t, buf, sizeof( buf ) );
698            printf( "  State: %s\n", buf );
699
700            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
701              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
702            {
703                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
704                printf( "  Percent Done: %s%%\n", buf );
705            }
706
707            if( tr_bencDictFindInt( t, "eta", &i ) )
708            {
709                etaToString( buf, sizeof( buf ), i );
710                printf( "  ETA: %s\n", buf );
711            }
712            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
713                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
714            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
715                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
716            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
717              && tr_bencDictFindInt( t, "haveValid", &j ) )
718            {
719                strlsize( buf, i + j, sizeof( buf ) );
720                strlsize( buf2, j, sizeof( buf2 ) );
721                printf( "  Have: %s (%s verified)\n", buf, buf2 );
722            }
723
724            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
725              && tr_bencDictFindInt( t, "totalSize", &j ) )
726            {
727                strlsize( buf, j, sizeof( buf ) );
728                strlsize( buf2, i, sizeof( buf2 ) );
729                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
730            }
731            if( tr_bencDictFindInt( t, "downloadedEver", &i )
732              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
733            {
734                strlsize( buf, i, sizeof( buf ) );
735                printf( "  Downloaded: %s\n", buf );
736                strlsize( buf, j, sizeof( buf ) );
737                printf( "  Uploaded: %s\n", buf );
738                strlratio( buf, j, i, sizeof( buf ) );
739                printf( "  Ratio: %s\n", buf );
740            }
741            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
742            {
743                strlsize( buf, i, sizeof( buf ) );
744                printf( "  Corrupt DL: %s\n", buf );
745            }
746            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
747                printf( "  Error: %s\n", str );
748
749            if( tr_bencDictFindInt( t, "peersConnected", &i )
750              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
751              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
752            {
753                printf(
754                    "  Peers: "
755                    "connected to %" PRId64 ", "
756                                            "uploading to %" PRId64
757                    ", "
758                    "downloading from %"
759                    PRId64 "\n",
760                    i, j, k );
761            }
762
763            if( tr_bencDictFindList( t, "webseeds", &l )
764              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
765            {
766                const int64_t n = tr_bencListSize( l );
767                if( n > 0 )
768                    printf(
769                        "  Web Seeds: downloading from %" PRId64 " of %"
770                        PRId64
771                        " web seeds\n", i, n );
772            }
773            printf( "\n" );
774
775            printf( "HISTORY\n" );
776            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
777            {
778                const time_t tt = i;
779                printf( "  Date added:      %s", ctime( &tt ) );
780            }
781            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
782            {
783                const time_t tt = i;
784                printf( "  Date finished:   %s", ctime( &tt ) );
785            }
786            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
787            {
788                const time_t tt = i;
789                printf( "  Date started:    %s", ctime( &tt ) );
790            }
791            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
792            {
793                const time_t tt = i;
794                printf( "  Latest activity: %s", ctime( &tt ) );
795            }
796            printf( "\n" );
797
798            printf( "TRACKER\n" );
799            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) && i )
800            {
801                const time_t tt = i;
802                printf( "  Latest announce: %s", ctime( &tt ) );
803            }
804            if( tr_bencDictFindStr( t, "announceURL", &str ) )
805                printf( "  Announce URL: %s\n", str );
806            if( tr_bencDictFindStr( t, "announceResponse",
807                                    &str ) && str && *str )
808                printf( "  Announce response: %s\n", str );
809            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) && i )
810            {
811                const time_t tt = i;
812                printf( "  Next announce:   %s", ctime( &tt ) );
813            }
814            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) && i )
815            {
816                const time_t tt = i;
817                printf( "  Latest scrape:   %s", ctime( &tt ) );
818            }
819            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
820                printf( "  Scrape response: %s\n", str );
821            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) && i )
822            {
823                const time_t tt = i;
824                printf( "  Next scrape:     %s", ctime( &tt ) );
825            }
826            if( tr_bencDictFindInt( t, "seeders", &i )
827              && tr_bencDictFindInt( t, "leechers", &j ) )
828                printf(
829                    "  Tracker knows of %" PRId64 " seeders and %" PRId64
830                    " leechers\n", i, j );
831            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
832                printf(
833                    "  Tracker has seen %" PRId64
834                    " clients complete this torrent\n", i );
835            printf( "\n" );
836
837            printf( "ORIGINS\n" );
838            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
839            {
840                const time_t tt = i;
841                printf( "  Date created: %s", ctime( &tt ) );
842            }
843            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
844                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
845            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
846                printf( "  Comment: %s\n", str );
847            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
848                printf( "  Creator: %s\n", str );
849            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
850                printf( "  Piece Count: %" PRId64 "\n", i );
851            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
852                printf( "  Piece Size: %" PRId64 "\n", i );
853        }
854    }
855}
856
857static void
858printFileList( tr_benc * top )
859{
860    tr_benc *args, *torrents;
861
862    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
863      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
864    {
865        int i, in;
866        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
867        {
868            tr_benc *    d = tr_bencListChild( torrents, i );
869            tr_benc *    files, *priorities, *wanteds;
870            const char * name;
871            if( tr_bencDictFindStr( d, "name", &name )
872              && tr_bencDictFindList( d, "files", &files )
873              && tr_bencDictFindList( d, "priorities", &priorities )
874              && tr_bencDictFindList( d, "wanted", &wanteds ) )
875            {
876                int j = 0, jn = tr_bencListSize( files );
877                printf( "%s (%d files):\n", name, jn );
878                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
879                        "Priority", "Get", "Size",
880                        "Name" );
881                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
882                {
883                    int64_t      have;
884                    int64_t      length;
885                    int64_t      priority;
886                    int64_t      wanted;
887                    const char * filename;
888                    tr_benc *    file = tr_bencListChild( files, j );
889                    if( tr_bencDictFindInt( file, "length", &length )
890                      && tr_bencDictFindStr( file, "name", &filename )
891                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
892                      && tr_bencGetInt( tr_bencListChild( priorities,
893                                                          j ), &priority )
894                      && tr_bencGetInt( tr_bencListChild( wanteds,
895                                                          j ), &wanted ) )
896                    {
897                        char         sizestr[64];
898                        double       percent = (double)have / length;
899                        const char * pristr;
900                        strlsize( sizestr, length, sizeof( sizestr ) );
901                        switch( priority )
902                        {
903                            case TR_PRI_LOW:
904                                pristr = "Low"; break;
905
906                            case TR_PRI_HIGH:
907                                pristr = "High"; break;
908
909                            default:
910                                pristr = "Normal"; break;
911                        }
912                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
913                                ( j + 1 ),
914                                ( 100.0 * percent ),
915                                pristr,
916                                ( wanted ? "Yes" : "No" ),
917                                sizestr,
918                                filename );
919                    }
920                }
921            }
922        }
923    }
924}
925
926static void
927printPeerList( tr_benc * top )
928{
929    tr_benc *args, *list;
930
931    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
932      && ( tr_bencDictFindList( args, "peers", &list ) ) )
933    {
934        int i, n;
935        printf( "%-20s  %-12s  %-5s  %5s  %s\n",
936                "Address", "Flags", "Down", "Up", "Client" );
937        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
938        {
939            const char * address, * client, * flagstr;
940            int64_t      rateToClient, rateToPeer;
941            tr_benc *    d = tr_bencListChild( list, i );
942            if( tr_bencDictFindStr( d, "address", &address )
943              && tr_bencDictFindStr( d, "client", &client )
944              && tr_bencDictFindStr( d, "flagstr", &flagstr )
945              && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
946              && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
947            {
948                printf( "%-20s  %-12s  %5.1f  %5.1f  %s\n",
949                        address, flagstr,
950                        rateToClient * 1024.0,
951                        rateToPeer * 1024.0,
952                        client );
953            }
954        }
955    }
956}
957
958static void
959printTorrentList( tr_benc * top )
960{
961    tr_benc *args, *list;
962
963    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
964      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
965    {
966        int i, n;
967        printf( "%-4s  %-4s  %-8s  %-6s  %-6s  %-5s  %-11s  %s\n",
968                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status",
969                "Name" );
970        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
971        {
972            int64_t     id, eta, status, up, down;
973            int64_t     sizeWhenDone, leftUntilDone;
974            int64_t     upEver, downEver;
975            const char *name;
976            tr_benc *   d = tr_bencListChild( list, i );
977            if( tr_bencDictFindInt( d, "downloadedEver", &downEver )
978              && tr_bencDictFindInt( d, "eta", &eta )
979              && tr_bencDictFindInt( d, "id", &id )
980              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
981              && tr_bencDictFindStr( d, "name", &name )
982              && tr_bencDictFindInt( d, "rateDownload", &down )
983              && tr_bencDictFindInt( d, "rateUpload", &up )
984              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
985              && tr_bencDictFindInt( d, "status", &status )
986              && tr_bencDictFindInt( d, "uploadedEver", &upEver ) )
987            {
988                char etaStr[16];
989                char statusStr[64];
990                if( leftUntilDone )
991                    etaToString( etaStr, sizeof( etaStr ), eta );
992                else
993                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
994                printf(
995                    "%4d  %3d%%  %-8s  %6.1f  %6.1f  %5.1f  %-11s  %s\n",
996                    (int)id,
997                    (int)( 100.0 *
998                           ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ),
999                    etaStr,
1000                    up / 1024.0,
1001                    down / 1024.0,
1002                    (double)( downEver ? ( (double)upEver /
1003                                          downEver ) : 0.0 ),
1004                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1005                    name );
1006            }
1007        }
1008    }
1009}
1010
1011static void
1012processResponse( const char * host,
1013                 int          port,
1014                 const void * response,
1015                 size_t       len )
1016{
1017    tr_benc top;
1018
1019    if( debug )
1020        fprintf( stderr, "got response: [%*.*s]\n",
1021                 (int)len, (int)len, (const char*) response );
1022
1023    if( tr_jsonParse( response, len, &top, NULL ) )
1024        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1025                 (int)len, (char*)response );
1026    else
1027    {
1028        int64_t      tag = -1;
1029        const char * str;
1030        tr_bencDictFindInt( &top, "tag", &tag );
1031
1032        switch( tag )
1033        {
1034            case TAG_FILES:
1035                printFileList( &top ); break;
1036
1037            case TAG_DETAILS:
1038                printDetails( &top ); break;
1039
1040            case TAG_LIST:
1041                printTorrentList( &top ); break;
1042
1043            case TAG_PEERS:
1044                printPeerList( &top ); break;
1045
1046            default:
1047                if( tr_bencDictFindStr( &top, "result", &str ) )
1048                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1049        }
1050
1051        tr_bencFree( &top );
1052    }
1053}
1054
1055static void
1056processRequests( const char *  host,
1057                 int           port,
1058                 const char ** reqs,
1059                 int           reqCount )
1060{
1061    int               i;
1062    CURL *            curl;
1063    struct evbuffer * buf = evbuffer_new( );
1064    char *            url = tr_strdup_printf(
1065        "http://%s:%d/transmission/rpc", host, port );
1066
1067    curl = curl_easy_init( );
1068    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1069#ifdef HAVE_LIBZ
1070    curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
1071#endif
1072    curl_easy_setopt( curl, CURLOPT_USERAGENT,
1073                      MY_NAME "/" LONG_VERSION_STRING );
1074    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1075    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
1076    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1077    curl_easy_setopt( curl, CURLOPT_URL, url );
1078    if( auth )
1079    {
1080        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1081        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1082    }
1083
1084    for( i = 0; i < reqCount; ++i )
1085    {
1086        CURLcode res;
1087        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1088        if( debug )
1089            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
1090        if( ( res = curl_easy_perform( curl ) ) )
1091            tr_nerr( MY_NAME, "(%s:%d) %s", host, port,
1092                    curl_easy_strerror( res ) );
1093        else
1094            processResponse( host, port, EVBUFFER_DATA(
1095                                buf ), EVBUFFER_LENGTH( buf ) );
1096
1097        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1098    }
1099
1100    /* cleanup */
1101    tr_free( url );
1102    evbuffer_free( buf );
1103    curl_easy_cleanup( curl );
1104}
1105
1106int
1107main( int     argc,
1108      char ** argv )
1109{
1110    int    i;
1111    int    port = DEFAULT_PORT;
1112    char * host = NULL;
1113
1114    if( argc < 2 )
1115        showUsage( );
1116
1117    getHostAndPort( &argc, argv, &host, &port );
1118    if( host == NULL )
1119        host = tr_strdup( DEFAULT_HOST );
1120
1121    readargs( argc, (const char**)argv );
1122    if( reqCount )
1123        processRequests( host, port, (const char**)reqs, reqCount );
1124    else
1125        showUsage( );
1126
1127    for( i = 0; i < reqCount; ++i )
1128        tr_free( reqs[i] );
1129
1130    tr_free( host );
1131    return 0;
1132}
1133
Note: See TracBrowser for help on using the repository browser.