source: trunk/daemon/remote.c @ 8889

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

(trunk) remove trailing spaces

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