source: trunk/daemon/remote.c @ 7006

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

bah, futz with the remote strings a little more.

  • Property svn:keywords set to Date Rev Author Id
File size: 37.7 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 7006 2008-11-01 14:59:49Z 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                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( toUs )
656                tr_strlcpy( buf, "Downloading", buflen );
657            else if( fromUs ) {
658                int64_t leftUntilDone = 0;
659                tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
660                if( leftUntilDone > 0 )
661                    tr_strlcpy( buf, "Uploading", buflen );
662                else
663                    tr_strlcpy( buf, "Seeding", buflen );
664            } else
665                tr_strlcpy( buf, "Idle", buflen );
666            break;
667        }
668    }
669
670    return buf;
671}
672
673static void
674printDetails( tr_benc * top )
675{
676    tr_benc *args, *torrents;
677
678    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
679      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
680    {
681        int ti, tCount;
682        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
683             ++ti )
684        {
685            tr_benc *    t = tr_bencListChild( torrents, ti );
686            tr_benc *    l;
687            const char * str;
688            char         buf[512];
689            char         buf2[512];
690            int64_t      i, j, k;
691
692            printf( "NAME\n" );
693            if( tr_bencDictFindInt( t, "id", &i ) )
694                printf( "  Id: %" PRId64 "\n", i );
695            if( tr_bencDictFindStr( t, "name", &str ) )
696                printf( "  Name: %s\n", str );
697            if( tr_bencDictFindStr( t, "hashString", &str ) )
698                printf( "  Hash: %s\n", str );
699            printf( "\n" );
700
701            printf( "TRANSFER\n" );
702            getStatusString( t, buf, sizeof( buf ) );
703            printf( "  State: %s\n", buf );
704
705            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
706              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
707            {
708                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
709                printf( "  Percent Done: %s%%\n", buf );
710            }
711
712            if( tr_bencDictFindInt( t, "eta", &i ) )
713            {
714                etaToString( buf, sizeof( buf ), i );
715                printf( "  ETA: %s\n", buf );
716            }
717            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
718                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
719            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
720                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
721            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
722              && tr_bencDictFindInt( t, "haveValid", &j ) )
723            {
724                strlsize( buf, i + j, sizeof( buf ) );
725                strlsize( buf2, j, sizeof( buf2 ) );
726                printf( "  Have: %s (%s verified)\n", buf, buf2 );
727            }
728
729            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
730              && tr_bencDictFindInt( t, "totalSize", &j ) )
731            {
732                strlsize( buf, j, sizeof( buf ) );
733                strlsize( buf2, i, sizeof( buf2 ) );
734                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
735            }
736            if( tr_bencDictFindInt( t, "downloadedEver", &i )
737              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
738            {
739                strlsize( buf, i, sizeof( buf ) );
740                printf( "  Downloaded: %s\n", buf );
741                strlsize( buf, j, sizeof( buf ) );
742                printf( "  Uploaded: %s\n", buf );
743                strlratio( buf, j, i, sizeof( buf ) );
744                printf( "  Ratio: %s\n", buf );
745            }
746            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
747            {
748                strlsize( buf, i, sizeof( buf ) );
749                printf( "  Corrupt DL: %s\n", buf );
750            }
751            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
752                printf( "  Error: %s\n", str );
753
754            if( tr_bencDictFindInt( t, "peersConnected", &i )
755              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
756              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
757            {
758                printf(
759                    "  Peers: "
760                    "connected to %" PRId64 ", "
761                                            "uploading to %" PRId64
762                    ", "
763                    "downloading from %"
764                    PRId64 "\n",
765                    i, j, k );
766            }
767
768            if( tr_bencDictFindList( t, "webseeds", &l )
769              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
770            {
771                const int64_t n = tr_bencListSize( l );
772                if( n > 0 )
773                    printf(
774                        "  Web Seeds: downloading from %" PRId64 " of %"
775                        PRId64
776                        " web seeds\n", i, n );
777            }
778            printf( "\n" );
779
780            printf( "HISTORY\n" );
781            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
782            {
783                const time_t tt = i;
784                printf( "  Date added:      %s", ctime( &tt ) );
785            }
786            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
787            {
788                const time_t tt = i;
789                printf( "  Date finished:   %s", ctime( &tt ) );
790            }
791            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
792            {
793                const time_t tt = i;
794                printf( "  Date started:    %s", ctime( &tt ) );
795            }
796            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
797            {
798                const time_t tt = i;
799                printf( "  Latest activity: %s", ctime( &tt ) );
800            }
801            printf( "\n" );
802
803            printf( "TRACKER\n" );
804            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) && i )
805            {
806                const time_t tt = i;
807                printf( "  Latest announce: %s", ctime( &tt ) );
808            }
809            if( tr_bencDictFindStr( t, "announceURL", &str ) )
810                printf( "  Announce URL: %s\n", str );
811            if( tr_bencDictFindStr( t, "announceResponse",
812                                    &str ) && str && *str )
813                printf( "  Announce response: %s\n", str );
814            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) && i )
815            {
816                const time_t tt = i;
817                printf( "  Next announce:   %s", ctime( &tt ) );
818            }
819            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) && i )
820            {
821                const time_t tt = i;
822                printf( "  Latest scrape:   %s", ctime( &tt ) );
823            }
824            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
825                printf( "  Scrape response: %s\n", str );
826            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) && i )
827            {
828                const time_t tt = i;
829                printf( "  Next scrape:     %s", ctime( &tt ) );
830            }
831            if( tr_bencDictFindInt( t, "seeders", &i )
832              && tr_bencDictFindInt( t, "leechers", &j ) )
833                printf(
834                    "  Tracker knows of %" PRId64 " seeders and %" PRId64
835                    " leechers\n", i, j );
836            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
837                printf(
838                    "  Tracker has seen %" PRId64
839                    " clients complete this torrent\n", i );
840            printf( "\n" );
841
842            printf( "ORIGINS\n" );
843            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
844            {
845                const time_t tt = i;
846                printf( "  Date created: %s", ctime( &tt ) );
847            }
848            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
849                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
850            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
851                printf( "  Comment: %s\n", str );
852            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
853                printf( "  Creator: %s\n", str );
854            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
855                printf( "  Piece Count: %" PRId64 "\n", i );
856            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
857                printf( "  Piece Size: %" PRId64 "\n", i );
858        }
859    }
860}
861
862static void
863printFileList( tr_benc * top )
864{
865    tr_benc *args, *torrents;
866
867    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
868      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
869    {
870        int i, in;
871        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
872        {
873            tr_benc *    d = tr_bencListChild( torrents, i );
874            tr_benc *    files, *priorities, *wanteds;
875            const char * name;
876            if( tr_bencDictFindStr( d, "name", &name )
877              && tr_bencDictFindList( d, "files", &files )
878              && tr_bencDictFindList( d, "priorities", &priorities )
879              && tr_bencDictFindList( d, "wanted", &wanteds ) )
880            {
881                int j = 0, jn = tr_bencListSize( files );
882                printf( "%s (%d files):\n", name, jn );
883                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
884                        "Priority", "Get", "Size",
885                        "Name" );
886                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
887                {
888                    int64_t      have;
889                    int64_t      length;
890                    int64_t      priority;
891                    int64_t      wanted;
892                    const char * filename;
893                    tr_benc *    file = tr_bencListChild( files, j );
894                    if( tr_bencDictFindInt( file, "length", &length )
895                      && tr_bencDictFindStr( file, "name", &filename )
896                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
897                      && tr_bencGetInt( tr_bencListChild( priorities,
898                                                          j ), &priority )
899                      && tr_bencGetInt( tr_bencListChild( wanteds,
900                                                          j ), &wanted ) )
901                    {
902                        char         sizestr[64];
903                        double       percent = (double)have / length;
904                        const char * pristr;
905                        strlsize( sizestr, length, sizeof( sizestr ) );
906                        switch( priority )
907                        {
908                            case TR_PRI_LOW:
909                                pristr = "Low"; break;
910
911                            case TR_PRI_HIGH:
912                                pristr = "High"; break;
913
914                            default:
915                                pristr = "Normal"; break;
916                        }
917                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
918                                ( j + 1 ),
919                                ( 100.0 * percent ),
920                                pristr,
921                                ( wanted ? "Yes" : "No" ),
922                                sizestr,
923                                filename );
924                    }
925                }
926            }
927        }
928    }
929}
930
931static void
932printPeerList( tr_benc * top )
933{
934    tr_benc *args, *list;
935
936    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
937      && ( tr_bencDictFindList( args, "peers", &list ) ) )
938    {
939        int i, n;
940        printf( "%-20s  %-12s  %-5s  %5s  %s\n",
941                "Address", "Flags", "Down", "Up", "Client" );
942        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
943        {
944            const char * address, * client, * flagstr;
945            int64_t      rateToClient, rateToPeer;
946            tr_benc *    d = tr_bencListChild( list, i );
947            if( tr_bencDictFindStr( d, "address", &address )
948              && tr_bencDictFindStr( d, "client", &client )
949              && tr_bencDictFindStr( d, "flagstr", &flagstr )
950              && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
951              && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
952            {
953                printf( "%-20s  %-12s  %5.1f  %5.1f  %s\n",
954                        address, flagstr,
955                        rateToClient * 1024.0,
956                        rateToPeer * 1024.0,
957                        client );
958            }
959        }
960    }
961}
962
963static void
964printTorrentList( tr_benc * top )
965{
966    tr_benc *args, *list;
967
968    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
969      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
970    {
971        int i, n;
972        printf( "%-4s  %-4s  %-8s  %-6s  %-6s  %-5s  %-11s  %s\n",
973                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status",
974                "Name" );
975        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
976        {
977            int64_t     id, eta, status, up, down;
978            int64_t     sizeWhenDone, leftUntilDone;
979            int64_t     upEver, downEver;
980            const char *name;
981            tr_benc *   d = tr_bencListChild( list, i );
982            if( tr_bencDictFindInt( d, "downloadedEver", &downEver )
983              && tr_bencDictFindInt( d, "eta", &eta )
984              && tr_bencDictFindInt( d, "id", &id )
985              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
986              && tr_bencDictFindStr( d, "name", &name )
987              && tr_bencDictFindInt( d, "rateDownload", &down )
988              && tr_bencDictFindInt( d, "rateUpload", &up )
989              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
990              && tr_bencDictFindInt( d, "status", &status )
991              && tr_bencDictFindInt( d, "uploadedEver", &upEver ) )
992            {
993                char etaStr[16];
994                char statusStr[64];
995                if( leftUntilDone )
996                    etaToString( etaStr, sizeof( etaStr ), eta );
997                else
998                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
999                printf(
1000                    "%4d  %3d%%  %-8s  %6.1f  %6.1f  %5.1f  %-11s  %s\n",
1001                    (int)id,
1002                    (int)( 100.0 *
1003                           ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ),
1004                    etaStr,
1005                    up / 1024.0,
1006                    down / 1024.0,
1007                    (double)( downEver ? ( (double)upEver /
1008                                          downEver ) : 0.0 ),
1009                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1010                    name );
1011            }
1012        }
1013    }
1014}
1015
1016static void
1017processResponse( const char * host,
1018                 int          port,
1019                 const void * response,
1020                 size_t       len )
1021{
1022    tr_benc top;
1023
1024    if( debug )
1025        fprintf( stderr, "got response: [%*.*s]\n",
1026                 (int)len, (int)len, (const char*) response );
1027
1028    if( tr_jsonParse( response, len, &top, NULL ) )
1029        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1030                 (int)len, (char*)response );
1031    else
1032    {
1033        int64_t      tag = -1;
1034        const char * str;
1035        tr_bencDictFindInt( &top, "tag", &tag );
1036
1037        switch( tag )
1038        {
1039            case TAG_FILES:
1040                printFileList( &top ); break;
1041
1042            case TAG_DETAILS:
1043                printDetails( &top ); break;
1044
1045            case TAG_LIST:
1046                printTorrentList( &top ); break;
1047
1048            case TAG_PEERS:
1049                printPeerList( &top ); break;
1050
1051            default:
1052                if( tr_bencDictFindStr( &top, "result", &str ) )
1053                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1054        }
1055
1056        tr_bencFree( &top );
1057    }
1058}
1059
1060static void
1061processRequests( const char *  host,
1062                 int           port,
1063                 const char ** reqs,
1064                 int           reqCount )
1065{
1066    int               i;
1067    CURL *            curl;
1068    struct evbuffer * buf = evbuffer_new( );
1069    char *            url = tr_strdup_printf(
1070        "http://%s:%d/transmission/rpc", host, port );
1071
1072    curl = curl_easy_init( );
1073    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1074#ifdef HAVE_LIBZ
1075    curl_easy_setopt( curl, CURLOPT_ENCODING, "deflate" );
1076#endif
1077    curl_easy_setopt( curl, CURLOPT_USERAGENT,
1078                      MY_NAME "/" LONG_VERSION_STRING );
1079    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1080    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
1081    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1082    curl_easy_setopt( curl, CURLOPT_URL, url );
1083    if( auth )
1084    {
1085        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1086        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1087    }
1088
1089    for( i = 0; i < reqCount; ++i )
1090    {
1091        CURLcode res;
1092        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1093        if( debug )
1094            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
1095        if( ( res = curl_easy_perform( curl ) ) )
1096            tr_nerr( MY_NAME, "(%s:%d) %s", host, port,
1097                    curl_easy_strerror( res ) );
1098        else
1099            processResponse( host, port, EVBUFFER_DATA(
1100                                buf ), EVBUFFER_LENGTH( buf ) );
1101
1102        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1103    }
1104
1105    /* cleanup */
1106    tr_free( url );
1107    evbuffer_free( buf );
1108    curl_easy_cleanup( curl );
1109}
1110
1111int
1112main( int     argc,
1113      char ** argv )
1114{
1115    int    i;
1116    int    port = DEFAULT_PORT;
1117    char * host = NULL;
1118
1119    if( argc < 2 )
1120        showUsage( );
1121
1122    getHostAndPort( &argc, argv, &host, &port );
1123    if( host == NULL )
1124        host = tr_strdup( DEFAULT_HOST );
1125
1126    readargs( argc, (const char**)argv );
1127    if( reqCount )
1128        processRequests( host, port, (const char**)reqs, reqCount );
1129    else
1130        showUsage( );
1131
1132    for( i = 0; i < reqCount; ++i )
1133        tr_free( reqs[i] );
1134
1135    tr_free( host );
1136    return 0;
1137}
1138
Note: See TracBrowser for help on using the repository browser.