source: branches/1.5x/daemon/remote.c @ 7898

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

(1.5x daemon) #1819: indicate torrent error in transmission-remote -l

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