source: branches/1.9x/daemon/remote.c @ 10582

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

(1.9x daemon) backport r10431 for #3070 "--blocklist-update timeout"

  • Property svn:keywords set to Date Rev Author Id
File size: 71.8 KB
Line 
1/*
2 * This file Copyright (C) 2008-2010 Mnemosyne LLC
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 10582 2010-05-01 04:36:01Z 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_STATS, TAG_LIST, TAG_DETAILS, TAG_FILES, TAG_PEERS, TAG_PORTTEST };
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    { 970, "alt-speed",             "Use the alternate Limits", "as",  0, NULL },
67    { 971, "no-alt-speed",          "Don't use the alternate Limits", "AS",  0, NULL },
68    { 972, "alt-speed-downlimit",   "max alternate download speed (in KB/s)", "asd",  1, "<speed>" },
69    { 973, "alt-speed-uplimit",     "max alternate upload speed (in KB/s)", "asu",  1, "<speed>" },
70    { 974, "alt-speed-scheduler",   "Use the scheduled on/off times", "asc",  0, NULL },
71    { 975, "no-alt-speed-scheduler","Don't use the scheduled on/off times", "ASC",  0, NULL },
72    { 976, "alt-speed-time-begin",  "Time to start using the alt speed limits (in hhmm)", NULL,  1, "<time>" },
73    { 977, "alt-speed-time-end",    "Time to stop using the alt speed limits (in hhmm)", NULL,  1, "<time>" },
74    { 978, "alt-speed-days",        "Numbers for any/all days of the week - eg. \"1-7\"", NULL,  1, "<days>" },
75    { 963, "blocklist-update",      "Blocklist update", NULL, 0, NULL },
76    { 'c', "incomplete-dir",        "Where to store new torrents until they're complete", "c", 1, "<dir>" },
77    { 'C', "no-incomplete-dir",     "Don't store incomplete torrents in a different location", "C", 0, NULL },
78    { 'b', "debug",                 "Print debugging information", "b",  0, NULL },
79    { 'd', "downlimit",             "Set the maximum global download speed in KB/s", "d",  1, "<speed>" },
80    { 'D', "no-downlimit",          "Don't limit the global download speed", "D",  0, NULL },
81    { 910, "encryption-required",   "Encrypt all peer connections", "er", 0, NULL },
82    { 911, "encryption-preferred",  "Prefer encrypted peer connections", "ep", 0, NULL },
83    { 912, "encryption-tolerated",  "Prefer unencrypted peer connections", "et", 0, NULL },
84    { 'f', "files",                 "List the current torrent(s)' files", "f",  0, NULL },
85    { 'g', "get",                   "Mark files for download", "g",  1, "<files>" },
86    { 'G', "no-get",                "Mark files for not downloading", "G",  1, "<files>" },
87    { 'i', "info",                  "Show the current torrent(s)' details", "i",  0, NULL },
88    { 920, "session-info",          "Show the session's details", "si", 0, NULL },
89    { 921, "session-stats",         "Show the session's statistics", "st", 0, NULL },
90    { 'l', "list",                  "List all torrents", "l",  0, NULL },
91    { 960, "move",                  "Move current torrent's data to a new folder", NULL, 1, "<path>" },
92    { 961, "find",                  "Tell Transmission where to find a torrent's data", NULL, 1, "<path>" },
93    { 'm', "portmap",               "Enable portmapping via NAT-PMP or UPnP", "m",  0, NULL },
94    { 'M', "no-portmap",            "Disable portmapping", "M",  0, NULL },
95    { 'n', "auth",                  "Set authentication info", "n",  1, "<username:password>" },
96    { 'N', "netrc",                 "Set authentication info from a .netrc file", "N",  1, "<filename>" },
97    { 'o', "dht",                   "Enable distributed hash tables (DHT)", "o", 0, NULL },
98    { 'O', "no-dht",                "Disable distributed hash tables (DHT)", "O", 0, NULL },
99    { 'p', "port",                  "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
100    { 962, "port-test",             "Port testing", "pt", 0, NULL },
101    { 'P', "random-port",           "Random port for incomping peers", "P", 0, NULL },
102    { 900, "priority-high",         "Set the files' priorities as high", "ph", 1, "<files>" },
103    { 901, "priority-normal",       "Set the files' priorities as normal", "pn", 1, "<files>" },
104    { 902, "priority-low",          "Set the files' priorities as low", "pl", 1, "<files>" },
105    { 'r', "remove",                "Remove the current torrent(s)", "r",  0, NULL },
106    { 930, "peers",                 "Set the current torrent(s)' maximum number of peers each", "pr", 1, "<max>" },
107    { 931, "global-peers",          "Set the global maximum number of peers", "gpr", 1, "<max>" },
108    { 'R', "remove-and-delete",     "Remove the current torrent(s) and delete local data", NULL, 0, NULL },
109    { 950, "seedratio",             "Let the current torrent(s) seed until a specific ratio", "sr", 1, "ratio" },
110    { 951, "seedratio-default",     "Let the current torrent(s) use the global seedratio settings", "srd", 0, NULL },
111    { 952, "no-seedratio",          "Let the current torrent(s) seed regardless of ratio", "SR", 0, NULL },
112    { 953, "global-seedratio",      "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio", "gsr", 1, "ratio" },
113    { 954, "no-global-seedratio",   "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio", "GSR", 0, NULL },
114    { 's', "start",                 "Start the current torrent(s)", "s",  0, NULL },
115    { 'S', "stop",                  "Stop the current torrent(s)", "S",  0, NULL },
116    { 't', "torrent",               "Set the current torrent(s)", "t",  1, "<torrent>" },
117    { 980, "torrent-downlimit",     "Set the maximum download speed for the current torrent(s) in KB/s", "td",  1, "<speed>" },
118    { 981, "no-torrent-downlimit",  "Don't limit the download speed for the current torrent(s)", "TD",  0, NULL },
119    { 982, "torrent-uplimit",       "Set the maximum upload speed for the current torrent(s) in KB/s", "tu",  1, "<speed>" },
120    { 983, "no-torrent-uplimit",    "Don't limit the upload speed for the current torrent(s)", "TU",  0, NULL },
121    { 984, "honor-session",         "Make the current torrent(s) honor the session limits", "hl",  0, NULL },
122    { 985, "no-honor-session",      "Make the current torrent(s) not honor the session limits", "HL",  0, NULL },
123    { 'u', "uplimit",               "Set the maximum global upload speed in KB/s", "u",  1, "<speed>" },
124    { 'U', "no-uplimit",            "Don't limit the global upload speed", "U",  0, NULL },
125    { 'v', "verify",                "Verify the current torrent(s)", "v",  0, NULL },
126    { 'V', "version",               "Show version number and exit", "V", 0, NULL },
127    { 'w', "download-dir",          "Set the default download folder", "w",  1, "<path>" },
128    { 'x', "pex",                   "Enable peer exchange (PEX)", "x",  0, NULL },
129    { 'X', "no-pex",                "Disable peer exchange (PEX)", "X",  0, NULL },
130    { 940, "peer-info",             "List the current torrent(s)' peers", "pi",  0, NULL },
131    {   0, NULL,                    NULL, NULL, 0, NULL }
132};
133
134static void
135showUsage( void )
136{
137    tr_getopt_usage( MY_NAME, getUsage( ), opts );
138}
139
140static int
141numarg( const char * arg )
142{
143    char *     end = NULL;
144    const long num = strtol( arg, &end, 10 );
145
146    if( *end )
147    {
148        fprintf( stderr, "Not a number: \"%s\"\n", arg );
149        showUsage( );
150        exit( EXIT_FAILURE );
151    }
152    return num;
153}
154
155static char * reqs[256]; /* arbitrary max */
156static int    reqCount = 0;
157static tr_bool debug = 0;
158static char * auth = NULL;
159static char * netrc = NULL;
160static char * sessionId = NULL;
161
162static char*
163tr_getcwd( void )
164{
165    char buf[2048];
166    *buf = '\0';
167#ifdef WIN32
168    _getcwd( buf, sizeof( buf ) );
169#else
170    getcwd( buf, sizeof( buf ) );
171#endif
172    return tr_strdup( buf );
173}
174
175static char*
176absolutify( const char * path )
177{
178    char * buf;
179
180    if( *path == '/' )
181        buf = tr_strdup( path );
182    else {
183        char * cwd = tr_getcwd( );
184        buf = tr_buildPath( cwd, path, NULL );
185        tr_free( cwd );
186    }
187
188    return buf;
189}
190
191static char*
192getEncodedMetainfo( const char * filename )
193{
194    size_t    len = 0;
195    char *    b64 = NULL;
196    uint8_t * buf = tr_loadFile( filename, &len );
197
198    if( buf )
199    {
200        b64 = tr_base64_encode( buf, len, NULL );
201        tr_free( buf );
202    }
203    return b64;
204}
205
206static void
207addIdArg( tr_benc *    args,
208          const char * id )
209{
210    if( !*id )
211    {
212        fprintf(
213            stderr,
214            "No torrent specified!  Please use the -t option first.\n" );
215        id = "-1"; /* no torrent will have this ID, so should be a no-op */
216    }
217    if( strcmp( id, "all" ) )
218    {
219        const char * pch;
220        tr_bool isList = strchr(id,',') || strchr(id,'-');
221        tr_bool isNum = TRUE;
222        for( pch=id; isNum && *pch; ++pch )
223            if( !isdigit( *pch ) )
224                isNum = FALSE;
225        if( isNum || isList )
226            tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen( id ) );
227        else
228            tr_bencDictAddStr( args, "ids", id ); /* it's a torrent sha hash */
229    }
230}
231
232static void
233addTime( tr_benc * args, const char * key, const char * arg )
234{
235    int time;
236    tr_bool success = FALSE;
237
238    if( arg && ( strlen( arg ) == 4 ) )
239    {
240        const char hh[3] = { arg[0], arg[1], '\0' };
241        const char mm[3] = { arg[2], arg[3], '\0' };
242        const int hour = atoi( hh );
243        const int min = atoi( mm );
244
245        if( 0<=hour && hour<24 && 0<=min && min<60 )
246        {
247            time = min + ( hour * 60 );
248            success = TRUE;
249        }
250    }
251
252    if( success )
253        tr_bencDictAddInt( args, key, time );
254    else
255        fprintf( stderr, "Please specify the time of day in 'hhmm' format.\n" );
256}
257
258static void
259addDays( tr_benc * args, const char * key, const char * arg )
260{
261    int days = 0;
262
263    if( arg )
264    {
265        int i;
266        int valueCount;
267        int * values = tr_parseNumberRange( arg, -1, &valueCount );
268        for( i=0; i<valueCount; ++i )
269        {
270            if ( values[i] < 0 || values[i] > 7 ) continue;
271            if ( values[i] == 7 ) values[i] = 0;
272
273            days |= 1 << values[i];
274        }
275        tr_free( values );
276    }
277
278    if ( days )
279        tr_bencDictAddInt( args, key, days );
280    else
281        fprintf( stderr, "Please specify the days of the week in '1-3,4,7' format.\n" );
282}
283
284static void
285addFiles( tr_benc *    args,
286          const char * key,
287          const char * arg )
288{
289    tr_benc * files = tr_bencDictAddList( args, key, 100 );
290
291    if( !*arg )
292    {
293        fprintf( stderr, "No files specified!\n" );
294        arg = "-1"; /* no file will have this index, so should be a no-op */
295    }
296    if( strcmp( arg, "all" ) )
297    {
298        int i;
299        int valueCount;
300        int * values = tr_parseNumberRange( arg, -1, &valueCount );
301        for( i=0; i<valueCount; ++i )
302            tr_bencListAddInt( files, values[i] );
303        tr_free( values );
304    }
305}
306
307#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
308
309static const char * files_keys[] = {
310    "files",
311    "name",
312    "priorities",
313    "wanted"
314};
315
316static const char * details_keys[] = {
317    "activityDate",
318    "addedDate",
319    "comment",
320    "corruptEver",
321    "creator",
322    "dateCreated",
323    "doneDate",
324    "downloadDir",
325    "downloadedEver",
326    "downloadLimit",
327    "downloadLimited",
328    "error",
329    "errorString",
330    "eta",
331    "hashString",
332    "haveUnchecked",
333    "haveValid",
334    "honorsSessionLimits",
335    "id",
336    "isPrivate",
337    "leftUntilDone",
338    "name",
339    "peersConnected",
340    "peersGettingFromUs",
341    "peersSendingToUs",
342    "peer-limit",
343    "pieceCount",
344    "pieceSize",
345    "rateDownload",
346    "rateUpload",
347    "recheckProgress",
348    "seedRatioMode",
349    "seedRatioLimit",
350    "sizeWhenDone",
351    "startDate",
352    "status",
353    "totalSize",
354    "trackerStats",
355    "uploadedEver",
356    "uploadLimit",
357    "uploadLimited",
358    "pieces",
359    "webseeds",
360    "webseedsSendingToUs"
361};
362
363static const char * list_keys[] = {
364    "error",
365    "errorString",
366    "eta",
367    "id",
368    "leftUntilDone",
369    "name",
370    "peersGettingFromUs",
371    "peersSendingToUs",
372    "rateDownload",
373    "rateUpload",
374    "sizeWhenDone",
375    "status",
376    "uploadRatio"
377};
378
379static int
380readargs( int argc, const char ** argv )
381{
382    int c;
383    tr_bool addingTorrents = FALSE;
384    int status = EXIT_SUCCESS;
385    char id[4096];
386    const char * optarg;
387
388    *id = '\0';
389
390    while( ( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg ) ) )
391    {
392        int     i, n;
393        int     addArg = TRUE;
394        tr_benc top, *args, *fields;
395        tr_bencInitDict( &top, 3 );
396        args = tr_bencDictAddDict( &top, "arguments", 0 );
397
398        switch( c )
399        {
400            case TR_OPT_UNK:
401                if( addingTorrents )
402                {
403                    char * tmp = getEncodedMetainfo( optarg );
404                    if( tmp )
405                    {
406                        tr_bencDictAddStr( &top, "method", "torrent-add" );
407                        tr_bencDictAddStr( args, "metainfo", tmp );
408                        tr_free( tmp );
409                    }
410                    else
411                    {
412                        tr_bencDictAddStr( &top, "method", "torrent-add" );
413                        tr_bencDictAddStr( args, "filename", optarg );
414                    }
415                }
416                else
417                {
418                    fprintf( stderr, "Unknown option: %s\n", optarg );
419                    addArg = FALSE;
420                    status |= EXIT_FAILURE;
421                }
422                break;
423
424            case 'a':
425                addingTorrents = TRUE;
426                addArg = FALSE;
427                break;
428
429            case 'b':
430                debug = TRUE;
431                addArg = FALSE;
432                break;
433
434            case 'c':
435                tr_bencDictAddStr( &top, "method", "session-set" );
436                tr_bencDictAddStr( args, TR_PREFS_KEY_INCOMPLETE_DIR, optarg );
437                tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, TRUE );
438                break;
439
440            case 'C':
441                tr_bencDictAddStr( &top, "method", "session-set" );
442                tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, FALSE );
443                break;
444
445            case 'd':
446                tr_bencDictAddStr( &top, "method", "session-set" );
447                tr_bencDictAddInt( args, TR_PREFS_KEY_DSPEED, numarg( optarg ) );
448                tr_bencDictAddBool( args, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
449                break;
450
451            case 'D':
452                tr_bencDictAddStr( &top, "method", "session-set" );
453                tr_bencDictAddBool( args, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
454                break;
455
456            case 'f':
457                tr_bencDictAddStr( &top, "method", "torrent-get" );
458                tr_bencDictAddInt( &top, "tag", TAG_FILES );
459                addIdArg( args, id );
460                n = TR_N_ELEMENTS( files_keys );
461                fields = tr_bencDictAddList( args, "fields", n );
462                for( i = 0; i < n; ++i )
463                    tr_bencListAddStr( fields, files_keys[i] );
464                break;
465
466            case 'g':
467                tr_bencDictAddStr( &top, "method", "torrent-set" );
468                addIdArg( args, id );
469                addFiles( args, "files-wanted", optarg );
470                break;
471
472            case 'G':
473                tr_bencDictAddStr( &top, "method", "torrent-set" );
474                addIdArg( args, id );
475                addFiles( args, "files-unwanted", optarg );
476                break;
477
478            case 'i':
479                tr_bencDictAddStr( &top, "method", "torrent-get" );
480                tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
481                addIdArg( args, id );
482                n = TR_N_ELEMENTS( details_keys );
483                fields = tr_bencDictAddList( args, "fields", n );
484                for( i = 0; i < n; ++i )
485                    tr_bencListAddStr( fields, details_keys[i] );
486                break;
487
488            case 'l':
489                tr_bencDictAddStr( &top, "method", "torrent-get" );
490                tr_bencDictAddInt( &top, "tag", TAG_LIST );
491                n = TR_N_ELEMENTS( list_keys );
492                fields = tr_bencDictAddList( args, "fields", n );
493                for( i = 0; i < n; ++i )
494                    tr_bencListAddStr( fields, list_keys[i] );
495                break;
496
497            case 'm':
498                tr_bencDictAddStr( &top, "method", "session-set" );
499                tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
500                break;
501
502            case 'M':
503                tr_bencDictAddStr( &top, "method", "session-set" );
504                tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
505                break;
506
507            case 'n':
508                auth = tr_strdup( optarg );
509                addArg = FALSE;
510                break;
511
512            case 'N':
513                netrc = tr_strdup( optarg );
514                addArg = FALSE;
515                break;
516
517            case 'p':
518                tr_bencDictAddStr( &top, "method", "session-set" );
519                tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_PORT, numarg( optarg ) );
520                break;
521
522            case 'P':
523                tr_bencDictAddStr( &top, "method", "session-set" );
524                tr_bencDictAddBool( args, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, TRUE);
525                break;
526
527            case 'r':
528                tr_bencDictAddStr( &top, "method", "torrent-remove" );
529                addIdArg( args, id );
530                break;
531
532            case 'R':
533                tr_bencDictAddStr( &top, "method", "torrent-remove" );
534                addIdArg( args, id );
535                tr_bencDictAddBool( args, "delete-local-data", TRUE );
536                break;
537
538            case 's':
539                tr_bencDictAddStr( &top, "method", "torrent-start" );
540                addIdArg( args, id );
541                break;
542
543            case 'S':
544                tr_bencDictAddStr( &top, "method", "torrent-stop" );
545                addIdArg( args, id );
546                break;
547
548            case 't':
549                tr_strlcpy( id, optarg, sizeof( id ) );
550                addArg = FALSE;
551                break;
552
553            case 'u':
554                tr_bencDictAddStr( &top, "method", "session-set" );
555                tr_bencDictAddInt( args, TR_PREFS_KEY_USPEED, numarg( optarg ) );
556                tr_bencDictAddBool( args, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
557                break;
558
559            case 'U':
560                tr_bencDictAddStr( &top, "method", "session-set" );
561                tr_bencDictAddBool( args, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
562                break;
563
564            case 'v':
565                tr_bencDictAddStr( &top, "method", "torrent-verify" );
566                addIdArg( args, id );
567                break;
568
569            case 'V':
570                fprintf( stderr, "Transmission %s\n", LONG_VERSION_STRING );
571                exit( 0 );
572                break;
573
574            case 'w': {
575                char * path = absolutify( optarg );
576                tr_bencDictAddStr( &top, "method", "session-set" );
577                tr_bencDictAddStr( args, TR_PREFS_KEY_DOWNLOAD_DIR, path );
578                tr_free( path );
579                break;
580            }
581
582            case 'o':
583                tr_bencDictAddStr( &top, "method", "session-set" );
584                tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, TRUE );
585                break;
586
587            case 'O':
588                tr_bencDictAddStr( &top, "method", "session-set" );
589                tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, FALSE );
590                break;
591
592            case 'x':
593                tr_bencDictAddStr( &top, "method", "session-set" );
594                tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, TRUE );
595                break;
596
597            case 'X':
598                tr_bencDictAddStr( &top, "method", "session-set" );
599                tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, FALSE );
600                break;
601
602            case 900:
603                tr_bencDictAddStr( &top, "method", "torrent-set" );
604                addIdArg( args, id );
605                addFiles( args, "priority-high", optarg );
606                break;
607
608            case 901:
609                tr_bencDictAddStr( &top, "method", "torrent-set" );
610                addIdArg( args, id );
611                addFiles( args, "priority-normal", optarg );
612                break;
613
614            case 902:
615                tr_bencDictAddStr( &top, "method", "torrent-set" );
616                addIdArg( args, id );
617                addFiles( args, "priority-low", optarg );
618                break;
619
620            case 910:
621                tr_bencDictAddStr( &top, "method", "session-set" );
622                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "required" );
623                break;
624
625            case 911:
626                tr_bencDictAddStr( &top, "method", "session-set" );
627                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "preferred" );
628                break;
629
630            case 912:
631                tr_bencDictAddStr( &top, "method", "session-set" );
632                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "tolerated" );
633                break;
634
635            case 920:
636                tr_bencDictAddStr( &top, "method", "session-get" );
637                tr_bencDictAddInt( &top, "tag", TAG_SESSION );
638                break;
639
640            case 921:
641                tr_bencDictAddStr( &top, "method", "session-stats" );
642                tr_bencDictAddInt( &top, "tag", TAG_STATS );
643                break;
644
645            case 930:
646                tr_bencDictAddStr( &top, "method", "torrent-set" );
647                addIdArg( args, id );
648                tr_bencDictAddInt( args, "peer-limit", atoi(optarg) );
649                break;
650
651            case 931:
652                tr_bencDictAddStr( &top, "method", "session-set" );
653                tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi(optarg) );
654                break;
655
656            case 940:
657                tr_bencDictAddStr( &top, "method", "torrent-get" );
658                tr_bencDictAddInt( &top, "tag", TAG_PEERS );
659                addIdArg( args, id );
660                fields = tr_bencDictAddList( args, "fields", 1 );
661                tr_bencListAddStr( fields, "peers" );
662                break;
663
664            case 950:
665                tr_bencDictAddStr( &top, "method", "torrent-set" );
666                tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
667                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_SINGLE );
668                addIdArg( args, id );
669                break;
670
671            case 951:
672                tr_bencDictAddStr( &top, "method", "torrent-set" );
673                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_GLOBAL );
674                addIdArg( args, id );
675                break;
676
677            case 952:
678                tr_bencDictAddStr( &top, "method", "torrent-set" );
679                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_UNLIMITED );
680                addIdArg( args, id );
681                break;
682
683            case 953:
684                tr_bencDictAddStr( &top, "method", "session-set" );
685                tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
686                tr_bencDictAddBool( args, "seedRatioLimited", TRUE );
687                break;
688
689            case 954:
690                tr_bencDictAddStr( &top, "method", "session-set" );
691                tr_bencDictAddBool( args, "seedRatioLimited", FALSE );
692                break;
693
694            case 960:
695                tr_bencDictAddStr( &top, "method", "torrent-set-location" );
696                tr_bencDictAddStr( args, "location", optarg );
697                tr_bencDictAddBool( args, "move", TRUE );
698                addIdArg( args, id );
699                break;
700
701            case 961:
702                tr_bencDictAddStr( &top, "method", "torrent-set-location" );
703                tr_bencDictAddStr( args, "location", optarg );
704                tr_bencDictAddBool( args, "move", FALSE );
705                addIdArg( args, id );
706                break;
707
708            case 962:
709                tr_bencDictAddStr( &top, "method", "port-test" );
710                tr_bencDictAddInt( &top, "tag", TAG_PORTTEST );
711                break;
712
713            case 963:
714                tr_bencDictAddStr( &top, "method", "blocklist-update" );
715                break;
716
717            case 970:
718                tr_bencDictAddStr( &top, "method", "session-set" );
719                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, TRUE );
720                break;
721
722            case 971:
723                tr_bencDictAddStr( &top, "method", "session-set" );
724                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, FALSE );
725                break;
726
727            case 972:
728                tr_bencDictAddStr( &top, "method", "session-set" );
729                tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_DOWN, numarg( optarg ) );
730                break;
731
732            case 973:
733                tr_bencDictAddStr( &top, "method", "session-set" );
734                tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_UP, numarg( optarg ) );
735                break;
736
737            case 974:
738                tr_bencDictAddStr( &top, "method", "session-set" );
739                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, TRUE );
740                break;
741
742            case 975:
743                tr_bencDictAddStr( &top, "method", "session-set" );
744                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, FALSE );
745                break;
746
747            case 976:
748                tr_bencDictAddStr( &top, "method", "session-set" );
749                addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, optarg);
750                break;
751
752            case 977:
753                tr_bencDictAddStr( &top, "method", "session-set" );
754                addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, optarg);
755                break;
756
757            case 978:
758                tr_bencDictAddStr( &top, "method", "session-set" );
759                addDays( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, optarg );
760                break;
761
762            case 980:
763                tr_bencDictAddStr( &top, "method", "torrent-set" );
764                addIdArg( args, id );
765                tr_bencDictAddInt( args, "downloadLimit", numarg( optarg ) );
766                tr_bencDictAddBool( args, "downloadLimited", TRUE );
767                break;
768
769            case 981:
770                tr_bencDictAddStr( &top, "method", "torrent-set" );
771                addIdArg( args, id );
772                tr_bencDictAddBool( args, "downloadLimited", FALSE );
773                break;
774
775            case 982:
776                tr_bencDictAddStr( &top, "method", "torrent-set" );
777                addIdArg( args, id );
778                tr_bencDictAddInt( args, "uploadLimit", numarg( optarg ) );
779                tr_bencDictAddBool( args, "uploadLimited", TRUE );
780                break;
781
782            case 983:
783                tr_bencDictAddStr( &top, "method", "torrent-set" );
784                addIdArg( args, id );
785                tr_bencDictAddBool( args, "uploadLimited", FALSE );
786                break;
787
788            case 984:
789                tr_bencDictAddStr( &top, "method", "torrent-set" );
790                addIdArg( args, id );
791                tr_bencDictAddBool( args, "honorsSessionLimits", TRUE );
792                break;
793
794            case 985:
795                tr_bencDictAddStr( &top, "method", "torrent-set" );
796                addIdArg( args, id );
797                tr_bencDictAddBool( args, "honorsSessionLimits", FALSE );
798                break;
799
800            case TR_OPT_ERR:
801                fprintf( stderr, "invalid option\n" );
802                showUsage( );
803                status |= EXIT_FAILURE;
804                break;
805
806            default:
807                fprintf( stderr, "got opt [%d]\n", (int)c );
808                showUsage( );
809                break;
810        }
811
812        if( addArg )
813        {
814            reqs[reqCount++] = tr_bencToStr( &top, TR_FMT_JSON_LEAN, NULL );
815        }
816
817        tr_bencFree( &top );
818    }
819
820    return status;
821}
822
823/* [host:port] or [host] or [port] */
824static void
825getHostAndPort( int * argc, char ** argv, char ** host, int * port )
826{
827    if( *argv[1] != '-' )
828    {
829        int          i;
830        const char * s = argv[1];
831        const char * delim = strchr( s, ':' );
832        if( delim )   /* user passed in both host and port */
833        {
834            *host = tr_strndup( s, delim - s );
835            *port = atoi( delim + 1 );
836        }
837        else
838        {
839            char *    end;
840            const int i = strtol( s, &end, 10 );
841            if( !*end ) /* user passed in a port */
842                *port = i;
843            else /* user passed in a host */
844                *host = tr_strdup( s );
845        }
846
847        *argc -= 1;
848        for( i = 1; i < *argc; ++i )
849            argv[i] = argv[i + 1];
850    }
851}
852
853static size_t
854writeFunc( void * ptr,
855           size_t size,
856           size_t nmemb,
857           void * buf )
858{
859    const size_t byteCount = size * nmemb;
860
861    evbuffer_add( buf, ptr, byteCount );
862    return byteCount;
863}
864
865
866static void
867etaToString( char *  buf, size_t  buflen, int64_t eta )
868{
869    if( eta < 0 )
870        tr_snprintf( buf, buflen, "Unknown" );
871    else if( eta < 60 )
872        tr_snprintf( buf, buflen, "%" PRId64 "sec", eta );
873    else if( eta < ( 60 * 60 ) )
874        tr_snprintf( buf, buflen, "%" PRId64 " min", eta / 60 );
875    else if( eta < ( 60 * 60 * 24 ) )
876        tr_snprintf( buf, buflen, "%" PRId64 " hrs", eta / ( 60 * 60 ) );
877    else
878        tr_snprintf( buf, buflen, "%" PRId64 " days", eta / ( 60 * 60 * 24 ) );
879}
880
881static char*
882tr_strltime( char * buf, int seconds, size_t buflen )
883{
884    int  days, hours, minutes;
885    char d[128], h[128], m[128], s[128];
886
887    if( seconds < 0 )
888        seconds = 0;
889
890    days = seconds / 86400;
891    hours = ( seconds % 86400 ) / 3600;
892    minutes = ( seconds % 3600 ) / 60;
893    seconds = ( seconds % 3600 ) % 60;
894
895    tr_snprintf( d, sizeof( d ), "%'d day%s", days, days==1?"":"s" );
896    tr_snprintf( h, sizeof( h ), "%'d hour%s", hours, hours==1?"":"s" );
897    tr_snprintf( m, sizeof( m ), "%'d minute%s", minutes, minutes==1?"":"s" );
898    tr_snprintf( s, sizeof( s ), "%'d second%s", seconds, seconds==1?"":"s" );
899
900    if( days )
901    {
902        if( days >= 4 || !hours )
903            tr_strlcpy( buf, d, buflen );
904        else
905            tr_snprintf( buf, buflen, "%s, %s", d, h );
906    }
907    else if( hours )
908    {
909        if( hours >= 4 || !minutes )
910            tr_strlcpy( buf, h, buflen );
911        else
912            tr_snprintf( buf, buflen, "%s, %s", h, m );
913    }
914    else if( minutes )
915    {
916        if( minutes >= 4 || !seconds )
917            tr_strlcpy( buf, m, buflen );
918        else
919            tr_snprintf( buf, buflen, "%s, %s", m, s );
920    }
921    else tr_strlcpy( buf, s, buflen );
922
923    return buf;
924}
925
926#define KILOBYTE_FACTOR 1024.0
927#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
928#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
929
930static char*
931strlratio2( char * buf, double ratio, size_t buflen )
932{
933    return tr_strratio( buf, buflen, ratio, "Inf" );
934}
935
936static char*
937strlratio( char * buf,
938           double numerator,
939           double denominator,
940           size_t buflen )
941{
942    double ratio;
943
944    if( denominator )
945        ratio = numerator / denominator;
946    else if( numerator )
947        ratio = TR_RATIO_INF;
948    else
949        ratio = TR_RATIO_NA;
950
951    return strlratio2( buf, ratio, buflen );
952}
953
954static char*
955strlsize( char *  buf, int64_t size, size_t  buflen )
956{
957    if( !size )
958        tr_strlcpy( buf, "None", buflen );
959    else if( size < (int64_t)KILOBYTE_FACTOR )
960        tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
961    else
962    {
963        double displayed_size;
964        if( size < (int64_t)MEGABYTE_FACTOR )
965        {
966            displayed_size = (double) size / KILOBYTE_FACTOR;
967            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
968        }
969        else if( size < (int64_t)GIGABYTE_FACTOR )
970        {
971            displayed_size = (double) size / MEGABYTE_FACTOR;
972            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
973        }
974        else
975        {
976            displayed_size = (double) size / GIGABYTE_FACTOR;
977            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
978        }
979    }
980    return buf;
981}
982
983static char*
984getStatusString( tr_benc * t, char * buf, size_t buflen )
985{
986    int64_t status;
987
988    if( !tr_bencDictFindInt( t, "status", &status ) )
989    {
990        *buf = '\0';
991    }
992    else switch( status )
993    {
994        case TR_STATUS_STOPPED:
995            tr_strlcpy( buf, "Stopped", buflen );
996            break;
997
998        case TR_STATUS_CHECK_WAIT:
999        case TR_STATUS_CHECK: {
1000            const char * str = status == TR_STATUS_CHECK_WAIT
1001                             ? "Will Verify"
1002                             : "Verifying";
1003            double percent;
1004            if( tr_bencDictFindReal( t, "recheckProgress", &percent ) )
1005                tr_snprintf( buf, buflen, "%s (%.0f%%)", str, floor(percent*100.0) );
1006            else
1007                tr_strlcpy( buf, str, buflen );
1008
1009            break;
1010        }
1011
1012        case TR_STATUS_DOWNLOAD:
1013        case TR_STATUS_SEED: {
1014            int64_t fromUs = 0;
1015            int64_t toUs = 0;
1016            tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
1017            tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
1018            if( fromUs && toUs )
1019                tr_strlcpy( buf, "Up & Down", buflen );
1020            else if( toUs )
1021                tr_strlcpy( buf, "Downloading", buflen );
1022            else if( fromUs ) {
1023                int64_t leftUntilDone = 0;
1024                tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
1025                if( leftUntilDone > 0 )
1026                    tr_strlcpy( buf, "Uploading", buflen );
1027                else
1028                    tr_strlcpy( buf, "Seeding", buflen );
1029            } else {
1030                tr_strlcpy( buf, "Idle", buflen );
1031            }
1032            break;
1033        }
1034    }
1035
1036    return buf;
1037}
1038
1039static void
1040printSession( tr_benc * top )
1041{
1042    tr_benc *args;
1043    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1044    {
1045        const char * str;
1046        int64_t      i;
1047        tr_bool      boolVal;
1048
1049        printf( "VERSION\n" );
1050        if( tr_bencDictFindStr( args,  "version", &str ) )
1051            printf( "  Daemon version: %s\n", str );
1052        if( tr_bencDictFindInt( args, "rpc-version", &i ) )
1053            printf( "  RPC version: %" PRId64 "\n", i );
1054        if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) )
1055            printf( "  RPC minimum version: %" PRId64 "\n", i );
1056        printf( "\n" );
1057
1058        printf( "CONFIG\n" );
1059        if( tr_bencDictFindStr( args, "config-dir", &str ) )
1060            printf( "  Configuration directory: %s\n", str );
1061        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
1062            printf( "  Download directory: %s\n", str );
1063        if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) )
1064            printf( "  Listenport: %" PRId64 "\n", i );
1065        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
1066            printf( "  Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1067        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
1068            printf( "  Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) );
1069        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_ENCRYPTION, &str ) )
1070            printf( "  Encryption: %s\n", str );
1071        printf( "\n" );
1072
1073        {
1074            tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled, seedRatioLimited;
1075            int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit;
1076            double seedRatioLimit;
1077
1078            if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN, &altDown ) &&
1079                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) &&
1080                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) &&
1081                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) &&
1082                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) &&
1083                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) &&
1084                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP, &altUp ) &&
1085                tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) &&
1086                tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED, &downLimit ) &&
1087                tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) &&
1088                tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED, &upLimit ) &&
1089                tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) &&
1090                tr_bencDictFindReal( args, "seedRatioLimit", &seedRatioLimit ) &&
1091                tr_bencDictFindBool( args, "seedRatioLimited", &seedRatioLimited) )
1092            {
1093                char buf[128];
1094
1095                printf( "LIMITS\n" );
1096                printf( "  Peer limit: %" PRId64 "\n", peerLimit );
1097
1098                if( seedRatioLimited )
1099                    tr_snprintf( buf, sizeof( buf ), "%.2f", seedRatioLimit );
1100                else
1101                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1102                printf( "  Default seed ratio limit: %s\n", buf );
1103
1104                if( altEnabled )
1105                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altUp );
1106                else if( upEnabled )
1107                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", upLimit );
1108                else
1109                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1110                printf( "  Upload speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
1111                        buf,
1112                        (upEnabled?"Enabled":"Disabled"), upLimit,
1113                        (altEnabled?"Enabled":"Disabled"), altUp );
1114
1115                if( altEnabled )
1116                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altDown );
1117                else if( downEnabled )
1118                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", downLimit );
1119                else
1120                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1121                printf( "  Download speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
1122                        buf,
1123                        (downEnabled?"Enabled":"Disabled"), downLimit,
1124                        (altEnabled?"Enabled":"Disabled"), altDown );
1125
1126                if( altTimeEnabled ) {
1127                    printf( "  Turtle schedule: %02d:%02d - %02d:%02d  ",
1128                            (int)(altBegin/60), (int)(altBegin%60),
1129                            (int)(altEnd/60), (int)(altEnd%60) );
1130                    if( altDay & TR_SCHED_SUN )   printf( "Sun " );
1131                    if( altDay & TR_SCHED_MON )   printf( "Mon " );
1132                    if( altDay & TR_SCHED_TUES )  printf( "Tue " );
1133                    if( altDay & TR_SCHED_WED )   printf( "Wed " );
1134                    if( altDay & TR_SCHED_THURS ) printf( "Thu " );
1135                    if( altDay & TR_SCHED_FRI )   printf( "Fri " );
1136                    if( altDay & TR_SCHED_SAT )   printf( "Sat " );
1137                    printf( "\n" );
1138                }
1139            }
1140        }
1141    }
1142}
1143
1144static void
1145printSessionStats( tr_benc * top )
1146{
1147    tr_benc *args, *d;
1148    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1149    {
1150        char buf[512];
1151        int64_t up, down, secs, sessions;
1152
1153        if( tr_bencDictFindDict( args, "current-stats", &d )
1154            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1155            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1156            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1157        {
1158            printf( "\nCURRENT SESSION\n" );
1159            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1160            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1161            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1162            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1163        }
1164
1165        if( tr_bencDictFindDict( args, "cumulative-stats", &d )
1166            && tr_bencDictFindInt( d, "sessionCount", &sessions )
1167            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1168            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1169            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1170        {
1171            printf( "\nTOTAL\n" );
1172            printf( "  Started %lu times\n", (unsigned long)sessions );
1173            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1174            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1175            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1176            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1177        }
1178    }
1179}
1180
1181static void
1182printDetails( tr_benc * top )
1183{
1184    tr_benc *args, *torrents;
1185
1186    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1187      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1188    {
1189        int ti, tCount;
1190        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
1191             ++ti )
1192        {
1193            tr_benc *    t = tr_bencListChild( torrents, ti );
1194            tr_benc *    l;
1195            const uint8_t * raw;
1196            size_t       rawlen;
1197            const char * str;
1198            char         buf[512];
1199            char         buf2[512];
1200            int64_t      i, j, k;
1201            tr_bool      boolVal;
1202            double       d;
1203
1204            printf( "NAME\n" );
1205            if( tr_bencDictFindInt( t, "id", &i ) )
1206                printf( "  Id: %" PRId64 "\n", i );
1207            if( tr_bencDictFindStr( t, "name", &str ) )
1208                printf( "  Name: %s\n", str );
1209            if( tr_bencDictFindStr( t, "hashString", &str ) )
1210                printf( "  Hash: %s\n", str );
1211            printf( "\n" );
1212
1213            printf( "TRANSFER\n" );
1214            getStatusString( t, buf, sizeof( buf ) );
1215            printf( "  State: %s\n", buf );
1216
1217            if( tr_bencDictFindStr( t, "downloadDir", &str ) )
1218                printf( "  Location: %s\n", str );
1219
1220            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1221              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
1222            {
1223                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
1224                printf( "  Percent Done: %s%%\n", buf );
1225            }
1226
1227            if( tr_bencDictFindInt( t, "eta", &i ) )
1228                printf( "  ETA: %s\n", tr_strltime( buf, i, sizeof( buf ) ) );
1229            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
1230                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
1231            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
1232                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
1233            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
1234              && tr_bencDictFindInt( t, "haveValid", &j ) )
1235            {
1236                strlsize( buf, i + j, sizeof( buf ) );
1237                strlsize( buf2, j, sizeof( buf2 ) );
1238                printf( "  Have: %s (%s verified)\n", buf, buf2 );
1239            }
1240
1241            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1242              && tr_bencDictFindInt( t, "totalSize", &j ) )
1243            {
1244                strlsize( buf, j, sizeof( buf ) );
1245                strlsize( buf2, i, sizeof( buf2 ) );
1246                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
1247            }
1248            if( tr_bencDictFindInt( t, "downloadedEver", &i )
1249              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
1250            {
1251                strlsize( buf, i, sizeof( buf ) );
1252                printf( "  Downloaded: %s\n", buf );
1253                strlsize( buf, j, sizeof( buf ) );
1254                printf( "  Uploaded: %s\n", buf );
1255                strlratio( buf, j, i, sizeof( buf ) );
1256                printf( "  Ratio: %s\n", buf );
1257            }
1258            if( tr_bencDictFindInt( t, "seedRatioMode", &i))
1259            {
1260                switch( i ) {
1261                    case TR_RATIOLIMIT_GLOBAL: 
1262                        printf( "  Ratio Limit: Default\n" );
1263                        break;
1264                    case TR_RATIOLIMIT_SINGLE:
1265                        if( tr_bencDictFindReal( t, "seedRatioLimit", &d))
1266                            printf( "  Ratio Limit: %.2f\n", d );
1267                        break;
1268                    case TR_RATIOLIMIT_UNLIMITED:
1269                        printf( "  Ratio Limit: Unlimited\n" );
1270                        break;
1271                    default: break;
1272                }
1273            }
1274            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
1275            {
1276                strlsize( buf, i, sizeof( buf ) );
1277                printf( "  Corrupt DL: %s\n", buf );
1278            }
1279            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str &&
1280                tr_bencDictFindInt( t, "error", &i ) && i )
1281            {
1282                switch( i ) {
1283                    case TR_STAT_TRACKER_WARNING: printf( "  Tracker gave a warning: %s\n", str ); break;
1284                    case TR_STAT_TRACKER_ERROR:   printf( "  Tracker gave an error: %s\n", str ); break;
1285                    case TR_STAT_LOCAL_ERROR:     printf( "  Error: %s\n", str ); break;
1286                    default: break; /* no error */
1287                }
1288            }
1289            if( tr_bencDictFindInt( t, "peersConnected", &i )
1290              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
1291              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
1292            {
1293                printf(
1294                    "  Peers: "
1295                    "connected to %" PRId64 ", "
1296                                            "uploading to %" PRId64
1297                    ", "
1298                    "downloading from %"
1299                    PRId64 "\n",
1300                    i, j, k );
1301            }
1302
1303            if( tr_bencDictFindList( t, "webseeds", &l )
1304              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
1305            {
1306                const int64_t n = tr_bencListSize( l );
1307                if( n > 0 )
1308                    printf(
1309                        "  Web Seeds: downloading from %" PRId64 " of %"
1310                        PRId64
1311                        " web seeds\n", i, n );
1312            }
1313            printf( "\n" );
1314
1315            printf( "HISTORY\n" );
1316            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
1317            {
1318                const time_t tt = i;
1319                printf( "  Date added:      %s", ctime( &tt ) );
1320            }
1321            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
1322            {
1323                const time_t tt = i;
1324                printf( "  Date finished:   %s", ctime( &tt ) );
1325            }
1326            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
1327            {
1328                const time_t tt = i;
1329                printf( "  Date started:    %s", ctime( &tt ) );
1330            }
1331            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
1332            {
1333                const time_t tt = i;
1334                printf( "  Latest activity: %s", ctime( &tt ) );
1335            }
1336            printf( "\n" );
1337
1338            printf( "TRACKERS\n" );
1339
1340            if( tr_bencDictFindList( t, "trackerStats", &l ) )
1341            {
1342                tr_benc * t;
1343                for( i=0; (( t = tr_bencListChild( l, i ))); ++i )
1344                {
1345                    int64_t downloadCount;
1346                    tr_bool hasAnnounced;
1347                    tr_bool hasScraped;
1348                    const char * host;
1349                    tr_bool isBackup;
1350                    int64_t lastAnnouncePeerCount;
1351                    const char * lastAnnounceResult;
1352                    int64_t lastAnnounceStartTime;
1353                    tr_bool lastAnnounceSucceeded;
1354                    int64_t lastAnnounceTime;
1355                    tr_bool lastAnnounceTimedOut;
1356                    const char * lastScrapeResult;
1357                    tr_bool lastScrapeSucceeded;
1358                    int64_t lastScrapeStartTime;
1359                    int64_t lastScrapeTime;
1360                    tr_bool lastScrapeTimedOut;
1361                    int64_t leecherCount;
1362                    int64_t nextAnnounceTime;
1363                    int64_t nextScrapeTime;
1364                    int64_t seederCount;
1365                    int64_t tier;
1366                    int64_t announceState;
1367                    int64_t scrapeState;
1368
1369                    if( tr_bencDictFindInt ( t, "downloadCount", &downloadCount ) &&
1370                        tr_bencDictFindBool( t, "hasAnnounced", &hasAnnounced ) &&
1371                        tr_bencDictFindBool( t, "hasScraped", &hasScraped ) &&
1372                        tr_bencDictFindStr ( t, "host", &host ) &&
1373                        tr_bencDictFindBool( t, "isBackup", &isBackup ) &&
1374                        tr_bencDictFindInt ( t, "announceState", &announceState ) &&
1375                        tr_bencDictFindInt ( t, "scrapeState", &scrapeState ) &&
1376                        tr_bencDictFindInt ( t, "lastAnnouncePeerCount", &lastAnnouncePeerCount ) &&
1377                        tr_bencDictFindStr ( t, "lastAnnounceResult", &lastAnnounceResult ) &&
1378                        tr_bencDictFindInt ( t, "lastAnnounceStartTime", &lastAnnounceStartTime ) &&
1379                        tr_bencDictFindBool( t, "lastAnnounceSucceeded", &lastAnnounceSucceeded ) &&
1380                        tr_bencDictFindInt ( t, "lastAnnounceTime", &lastAnnounceTime ) &&
1381                        tr_bencDictFindBool( t, "lastAnnounceTimedOut", &lastAnnounceTimedOut ) &&
1382                        tr_bencDictFindStr ( t, "lastScrapeResult", &lastScrapeResult ) &&
1383                        tr_bencDictFindInt ( t, "lastScrapeStartTime", &lastScrapeStartTime ) &&
1384                        tr_bencDictFindBool( t, "lastScrapeSucceeded", &lastScrapeSucceeded ) &&
1385                        tr_bencDictFindInt ( t, "lastScrapeTime", &lastScrapeTime ) &&
1386                        tr_bencDictFindBool( t, "lastScrapeTimedOut", &lastScrapeTimedOut ) &&
1387                        tr_bencDictFindInt ( t, "leecherCount", &leecherCount ) &&
1388                        tr_bencDictFindInt ( t, "nextAnnounceTime", &nextAnnounceTime ) &&
1389                        tr_bencDictFindInt ( t, "nextScrapeTime", &nextScrapeTime ) &&
1390                        tr_bencDictFindInt ( t, "seederCount", &seederCount ) &&
1391                        tr_bencDictFindInt ( t, "tier", &tier ) )
1392                    {
1393                        const time_t now = time( NULL );
1394
1395                        printf( "\n" );
1396                        printf( "  Tracker #%d: %s\n", (int)(i+1), host );
1397                        if( isBackup )
1398                          printf( "  Backup on tier #%d\n", (int)tier );
1399                        else
1400                          printf( "  Active in tier #%d\n", (int)tier );
1401
1402                        if( !isBackup )
1403                        {
1404                            if( hasAnnounced )
1405                            {
1406                                tr_strltime( buf, now - lastAnnounceTime, sizeof( buf ) );
1407                                if( lastAnnounceSucceeded )
1408                                    printf( "  Got a list of %'d peers %s ago\n",
1409                                            (int)lastAnnouncePeerCount, buf );
1410                                else if( lastAnnounceTimedOut )
1411                                    printf( "  Peer list request timed out; will retry\n" );
1412                                else
1413                                    printf( "  Got an error \"%s\" %s ago\n",
1414                                            lastAnnounceResult, buf );
1415                            }
1416
1417                            switch( announceState )
1418                            {
1419                                case TR_TRACKER_INACTIVE:
1420                                    printf( "  No updates scheduled\n" );
1421                                    break;
1422                                case TR_TRACKER_WAITING:
1423                                    tr_strltime( buf, nextAnnounceTime - now, sizeof( buf ) );
1424                                    printf( "  Asking for more peers in %s\n", buf );
1425                                    break;
1426                                case TR_TRACKER_QUEUED:
1427                                    printf( "  Queued to ask for more peers\n" );
1428                                    break;
1429                                case TR_TRACKER_ACTIVE:
1430                                    tr_strltime( buf, now - lastAnnounceStartTime, sizeof( buf ) );
1431                                    printf( "  Asking for more peers now... %s\n", buf );
1432                                    break;
1433                            }
1434
1435                            if( hasScraped )
1436                            {
1437                                tr_strltime( buf, now - lastScrapeTime, sizeof( buf ) );
1438                                if( lastScrapeSucceeded )
1439                                    printf( "  Tracker had %'d seeders and %'d leechers %s ago\n",
1440                                            (int)seederCount, (int)leecherCount, buf );
1441                                else if( lastScrapeTimedOut )
1442                                    printf( "  Tracker scrape timed out; will retry\n" );
1443                                else
1444                                    printf( "  Got a scrape error \"%s\" %s ago\n",
1445                                            lastScrapeResult, buf );
1446                            }
1447
1448                            switch( scrapeState )
1449                            {
1450                                case TR_TRACKER_INACTIVE:
1451                                    break;
1452                                case TR_TRACKER_WAITING:
1453                                    tr_strltime( buf, nextScrapeTime - now, sizeof( buf ) );
1454                                    printf( "  Asking for peer counts in %s\n", buf );
1455                                    break;
1456                                case TR_TRACKER_QUEUED:
1457                                    printf( "  Queued to ask for peer counts\n" );
1458                                    break;
1459                                case TR_TRACKER_ACTIVE:
1460                                    tr_strltime( buf, now - lastScrapeStartTime, sizeof( buf ) );
1461                                    printf( "  Asking for peer counts now... %s\n", buf );
1462                                    break;
1463                            }
1464                        }
1465                    }
1466                }
1467                printf( "\n" );
1468            }
1469
1470            printf( "ORIGINS\n" );
1471            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
1472            {
1473                const time_t tt = i;
1474                printf( "  Date created: %s", ctime( &tt ) );
1475            }
1476            if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) )
1477                printf( "  Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) );
1478            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
1479                printf( "  Comment: %s\n", str );
1480            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
1481                printf( "  Creator: %s\n", str );
1482            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
1483                printf( "  Piece Count: %" PRId64 "\n", i );
1484            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
1485                printf( "  Piece Size: %" PRId64 "\n", i );
1486            printf( "\n" );
1487
1488            printf( "LIMITS\n" );
1489            if( tr_bencDictFindBool( t, "downloadLimited", &boolVal )
1490                && tr_bencDictFindInt( t, "downloadLimit", &i ) )
1491            {
1492                printf( "  Download Limit: " );
1493                if( boolVal )
1494                    printf( "%" PRId64 " KB/s\n", i );
1495                else
1496                    printf( "Unlimited\n" );
1497            }
1498            if( tr_bencDictFindBool( t, "uploadLimited", &boolVal )
1499                && tr_bencDictFindInt( t, "uploadLimit", &i ) )
1500            {
1501                printf( "  Upload Limit: " );
1502                if( boolVal )
1503                    printf( "%" PRId64 " KB/s\n", i );
1504                else
1505                    printf( "Unlimited\n" );
1506            }
1507            if( tr_bencDictFindBool( t, "honorsSessionLimits", &boolVal ) )
1508                printf( "  Honors Session Limits: %s\n", ( boolVal ? "Yes" : "No" ) );
1509            if( tr_bencDictFindInt ( t, "peer-limit", &i ) )
1510                printf( "  Peer limit: %" PRId64 "\n", i );
1511            printf( "\n" );
1512
1513            printf( "PIECES\n" );
1514            if( tr_bencDictFindRaw( t, "pieces", &raw, &rawlen ) && tr_bencDictFindInt( t, "pieceCount", &j ) ) {
1515                int len;
1516                char * str = tr_base64_decode( raw, rawlen, &len );
1517                printf( "  " );
1518                for( i=k=0; k<len; ++k ) {
1519                    int e;
1520                    for( e=0; i<j && e<8; ++e, ++i )
1521                        printf( "%c", str[k] & (1<<(7-e)) ? '1' : '0' );
1522                    printf( " " );
1523                    if( !(i%64) )
1524                        printf( "\n  " );
1525                }
1526                tr_free( str );
1527            }
1528            printf( "\n" );
1529        }
1530    }
1531}
1532
1533static void
1534printFileList( tr_benc * top )
1535{
1536    tr_benc *args, *torrents;
1537
1538    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1539      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1540    {
1541        int i, in;
1542        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
1543        {
1544            tr_benc *    d = tr_bencListChild( torrents, i );
1545            tr_benc *    files, *priorities, *wanteds;
1546            const char * name;
1547            if( tr_bencDictFindStr( d, "name", &name )
1548              && tr_bencDictFindList( d, "files", &files )
1549              && tr_bencDictFindList( d, "priorities", &priorities )
1550              && tr_bencDictFindList( d, "wanted", &wanteds ) )
1551            {
1552                int j = 0, jn = tr_bencListSize( files );
1553                printf( "%s (%d files):\n", name, jn );
1554                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
1555                        "Priority", "Get", "Size",
1556                        "Name" );
1557                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1558                {
1559                    int64_t      have;
1560                    int64_t      length;
1561                    int64_t      priority;
1562                    int64_t      wanted;
1563                    const char * filename;
1564                    tr_benc *    file = tr_bencListChild( files, j );
1565                    if( tr_bencDictFindInt( file, "length", &length )
1566                      && tr_bencDictFindStr( file, "name", &filename )
1567                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
1568                      && tr_bencGetInt( tr_bencListChild( priorities,
1569                                                          j ), &priority )
1570                      && tr_bencGetInt( tr_bencListChild( wanteds,
1571                                                          j ), &wanted ) )
1572                    {
1573                        char         sizestr[64];
1574                        double       percent = (double)have / length;
1575                        const char * pristr;
1576                        strlsize( sizestr, length, sizeof( sizestr ) );
1577                        switch( priority )
1578                        {
1579                            case TR_PRI_LOW:
1580                                pristr = "Low"; break;
1581
1582                            case TR_PRI_HIGH:
1583                                pristr = "High"; break;
1584
1585                            default:
1586                                pristr = "Normal"; break;
1587                        }
1588                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
1589                                j,
1590                                floor( 100.0 * percent ),
1591                                pristr,
1592                                ( wanted ? "Yes" : "No" ),
1593                                sizestr,
1594                                filename );
1595                    }
1596                }
1597            }
1598        }
1599    }
1600}
1601
1602static void
1603printPeersImpl( tr_benc * peers )
1604{
1605    int i, n;
1606    printf( "%-20s  %-12s  %-5s %-6s  %-6s  %s\n",
1607            "Address", "Flags", "Done", "Down", "Up", "Client" );
1608    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1609    {
1610        double progress;
1611        const char * address, * client, * flagstr;
1612        int64_t rateToClient, rateToPeer;
1613        tr_benc * d = tr_bencListChild( peers, i );
1614
1615        if( tr_bencDictFindStr( d, "address", &address )
1616          && tr_bencDictFindStr( d, "clientName", &client )
1617          && tr_bencDictFindReal( d, "progress", &progress )
1618          && tr_bencDictFindStr( d, "flagStr", &flagstr )
1619          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1620          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1621        {
1622            printf( "%-20s  %-12s  %-5.1f %6.1f  %6.1f  %s\n",
1623                    address, flagstr, (progress*100.0),
1624                    rateToClient / 1024.0,
1625                    rateToPeer / 1024.0,
1626                    client );
1627        }
1628    }
1629}
1630
1631static void
1632printPeers( tr_benc * top )
1633{
1634    tr_benc *args, *torrents;
1635
1636    if( tr_bencDictFindDict( top, "arguments", &args )
1637      && tr_bencDictFindList( args, "torrents", &torrents ) )
1638    {
1639        int i, n;
1640        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1641        {
1642            tr_benc * peers;
1643            tr_benc * torrent = tr_bencListChild( torrents, i );
1644            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1645                printPeersImpl( peers );
1646                if( i+1<n )
1647                    printf( "\n" );
1648            }
1649        }
1650    }
1651}
1652
1653static void
1654printPortTest( tr_benc * top )
1655{
1656    tr_benc *args;
1657    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1658    {
1659        tr_bool      boolVal;
1660
1661        if( tr_bencDictFindBool( args, "port-is-open", &boolVal ) )
1662            printf( "Port is open: %s\n", ( boolVal ? "Yes" : "No" ) );
1663    }
1664}
1665
1666static void
1667printTorrentList( tr_benc * top )
1668{
1669    tr_benc *args, *list;
1670
1671    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1672      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1673    {
1674        int i, n;
1675        int64_t total_up = 0, total_down = 0, total_size = 0;
1676        char haveStr[32];
1677
1678        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1679                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1680                "Name" );
1681
1682        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1683        {
1684            int64_t      id, eta, status, up, down;
1685            int64_t      sizeWhenDone, leftUntilDone;
1686            double       ratio;
1687            const char * name;
1688            tr_benc *   d = tr_bencListChild( list, i );
1689            if( tr_bencDictFindInt( d, "eta", &eta )
1690              && tr_bencDictFindInt( d, "id", &id )
1691              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1692              && tr_bencDictFindStr( d, "name", &name )
1693              && tr_bencDictFindInt( d, "rateDownload", &down )
1694              && tr_bencDictFindInt( d, "rateUpload", &up )
1695              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1696              && tr_bencDictFindInt( d, "status", &status )
1697              && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1698            {
1699                char etaStr[16];
1700                char statusStr[64];
1701                char ratioStr[32];
1702                char doneStr[8];
1703                int64_t error;
1704                char errorMark;
1705
1706                if( sizeWhenDone )
1707                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1708                else
1709                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1710
1711                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1712
1713                if( leftUntilDone || eta != -1 )
1714                    etaToString( etaStr, sizeof( etaStr ), eta );
1715                else
1716                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1717                if( tr_bencDictFindInt( d, "error", &error ) && error )
1718                    errorMark = '*';
1719                else
1720                    errorMark = ' ';
1721                printf(
1722                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1723                    (int)id, errorMark,
1724                    doneStr,
1725                    haveStr,
1726                    etaStr,
1727                    up / 1024.0,
1728                    down / 1024.0,
1729                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1730                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1731                    name );
1732
1733                total_up += up;
1734                total_down += down;
1735                total_size += sizeWhenDone - leftUntilDone;
1736            }
1737        }
1738
1739        printf( "Sum:         %9s            %6.1f  %6.1f\n",
1740                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1741                total_up / 1024.0,
1742                total_down / 1024.0 );
1743    }
1744}
1745
1746static int
1747processResponse( const char * host,
1748                 int          port,
1749                 const void * response,
1750                 size_t       len )
1751{
1752    tr_benc top;
1753    int status = EXIT_SUCCESS;
1754
1755    if( debug )
1756        fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1757                 (int)len, (int)len, (int)len, (const char*) response );
1758
1759    if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1760    {
1761        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1762                 (int)len, (char*)response );
1763        status |= EXIT_FAILURE;
1764    }
1765    else
1766    {
1767        int64_t      tag = -1;
1768        const char * str;
1769        tr_bencDictFindInt( &top, "tag", &tag );
1770
1771        switch( tag )
1772        {
1773            case TAG_SESSION:
1774                printSession( &top ); break;
1775
1776            case TAG_STATS:
1777                printSessionStats( &top ); break;
1778
1779            case TAG_FILES:
1780                printFileList( &top ); break;
1781
1782            case TAG_DETAILS:
1783                printDetails( &top ); break;
1784
1785            case TAG_LIST:
1786                printTorrentList( &top ); break;
1787
1788            case TAG_PEERS:
1789                printPeers( &top ); break;
1790
1791            case TAG_PORTTEST:
1792                printPortTest( &top ); break;
1793
1794            default:
1795                if( !tr_bencDictFindStr( &top, "result", &str ) )
1796                    status |= EXIT_FAILURE;
1797                else {
1798                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1799                    if( strcmp( str, "success") )
1800                        status |= EXIT_FAILURE;
1801                }
1802        }
1803
1804        tr_bencFree( &top );
1805    }
1806
1807    return status;
1808}
1809
1810/* look for a session id in the header in case the server gives back a 409 */
1811static size_t
1812parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
1813{
1814    const char * line = ptr;
1815    const size_t line_len = size * nmemb;
1816    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
1817    const size_t key_len = strlen( key );
1818
1819    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
1820    {
1821        const char * begin = line + key_len;
1822        const char * end = begin;
1823        while( !isspace( *end ) )
1824            ++end;
1825        tr_free( sessionId );
1826        sessionId = tr_strndup( begin, end-begin );
1827    }
1828
1829    return line_len;
1830}
1831
1832static CURL*
1833tr_curl_easy_init( struct evbuffer * writebuf )
1834{
1835    CURL * curl = curl_easy_init( );
1836    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1837    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1838    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1839    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1840    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1841    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1842    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1843    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1844    curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1845    if( netrc )
1846        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1847    if( auth )
1848        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1849    if( sessionId ) {
1850        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1851        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1852        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1853        /* fixme: leaks */
1854    }
1855    return curl;
1856}
1857
1858static long
1859getTimeoutSecs( const char * req )
1860{
1861    if( strstr( req, "\"method\":\"blocklist-update\"" ) != NULL ) 
1862        return 300L; 
1863
1864    return 60L; /* default value */ 
1865} 
1866
1867static int
1868processRequests( const char *  host,
1869                 int           port,
1870                 const char ** reqs,
1871                 int           reqCount )
1872{
1873    int i;
1874    CURL * curl = NULL;
1875    struct evbuffer * buf = evbuffer_new( );
1876    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1877    int status = EXIT_SUCCESS;
1878
1879    for( i=0; i<reqCount; ++i )
1880    {
1881        CURLcode res;
1882        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1883
1884        if( curl == NULL )
1885        {
1886            curl = tr_curl_easy_init( buf );
1887            curl_easy_setopt( curl, CURLOPT_URL, url );
1888        }
1889
1890        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1891        curl_easy_setopt( curl, CURLOPT_TIMEOUT, getTimeoutSecs( reqs[i] ) );
1892
1893        if( debug )
1894            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
1895        if( ( res = curl_easy_perform( curl ) ) )
1896        {
1897            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
1898            status |= EXIT_FAILURE;
1899        }
1900        else {
1901            long response;
1902            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1903            switch( response ) {
1904                case 200:
1905                    status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1906                    break;
1907                case 409:
1908                    /* session id failed.  our curl header func has already
1909                     * pulled the new session id from this response's headers,
1910                     * build a new CURL* and try again */
1911                    curl_easy_cleanup( curl );
1912                    curl = NULL;
1913                    --i;
1914                    break;
1915                default:
1916                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1917                    status |= EXIT_FAILURE;
1918                    break;
1919            }
1920        }
1921    }
1922
1923    /* cleanup */
1924    tr_free( url );
1925    evbuffer_free( buf );
1926    if( curl != NULL )
1927        curl_easy_cleanup( curl );
1928    return status;
1929}
1930
1931int
1932main( int     argc,
1933      char ** argv )
1934{
1935    int    i;
1936    int    port = DEFAULT_PORT;
1937    char * host = NULL;
1938    int    exit_status = EXIT_SUCCESS;
1939
1940    if( argc < 2 ) {
1941        showUsage( );
1942        return EXIT_FAILURE;
1943    }
1944
1945    getHostAndPort( &argc, argv, &host, &port );
1946    if( host == NULL )
1947        host = tr_strdup( DEFAULT_HOST );
1948
1949    readargs( argc, (const char**)argv );
1950    if( reqCount == 0 ) {
1951        showUsage( );
1952        return EXIT_FAILURE;
1953    }
1954
1955    exit_status = processRequests( host, port, (const char**)reqs, reqCount );
1956
1957    for( i=0; i<reqCount; ++i )
1958        tr_free( reqs[i] );
1959
1960    tr_free( host );
1961    return exit_status;
1962}
1963
Note: See TracBrowser for help on using the repository browser.