source: trunk/daemon/remote.c @ 10128

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

(trunk daemon) #2872 "add support for displaying seed ratios in transmission-remote" -- implemented in trunk for 1.90

  • Property svn:keywords set to Date Rev Author Id
File size: 71.3 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 10128 2010-02-07 16:41:45Z 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                    int64_t leecherCount;
1361                    int64_t nextAnnounceTime;
1362                    int64_t nextScrapeTime;
1363                    int64_t seederCount;
1364                    int64_t tier;
1365                    int64_t announceState;
1366                    int64_t scrapeState;
1367
1368                    if( tr_bencDictFindInt ( t, "downloadCount", &downloadCount ) &&
1369                        tr_bencDictFindBool( t, "hasAnnounced", &hasAnnounced ) &&
1370                        tr_bencDictFindBool( t, "hasScraped", &hasScraped ) &&
1371                        tr_bencDictFindStr ( t, "host", &host ) &&
1372                        tr_bencDictFindBool( t, "isBackup", &isBackup ) &&
1373                        tr_bencDictFindInt ( t, "announceState", &announceState ) &&
1374                        tr_bencDictFindInt ( t, "scrapeState", &scrapeState ) &&
1375                        tr_bencDictFindInt ( t, "lastAnnouncePeerCount", &lastAnnouncePeerCount ) &&
1376                        tr_bencDictFindStr ( t, "lastAnnounceResult", &lastAnnounceResult ) &&
1377                        tr_bencDictFindInt ( t, "lastAnnounceStartTime", &lastAnnounceStartTime ) &&
1378                        tr_bencDictFindBool( t, "lastAnnounceSucceeded", &lastAnnounceSucceeded ) &&
1379                        tr_bencDictFindInt ( t, "lastAnnounceTime", &lastAnnounceTime ) &&
1380                        tr_bencDictFindBool( t, "lastAnnounceTimedOut", &lastAnnounceTimedOut ) &&
1381                        tr_bencDictFindStr ( t, "lastScrapeResult", &lastScrapeResult ) &&
1382                        tr_bencDictFindInt ( t, "lastScrapeStartTime", &lastScrapeStartTime ) &&
1383                        tr_bencDictFindBool( t, "lastScrapeSucceeded", &lastScrapeSucceeded ) &&
1384                        tr_bencDictFindInt ( t, "lastScrapeTime", &lastScrapeTime ) &&
1385                        tr_bencDictFindInt ( t, "leecherCount", &leecherCount ) &&
1386                        tr_bencDictFindInt ( t, "nextAnnounceTime", &nextAnnounceTime ) &&
1387                        tr_bencDictFindInt ( t, "nextScrapeTime", &nextScrapeTime ) &&
1388                        tr_bencDictFindInt ( t, "seederCount", &seederCount ) &&
1389                        tr_bencDictFindInt ( t, "tier", &tier ) )
1390                    {
1391                        const time_t now = time( NULL );
1392
1393                        printf( "\n" );
1394                        printf( "  Tracker #%d: %s\n", (int)(i+1), host );
1395                        if( isBackup )
1396                          printf( "  Backup on tier #%d\n", (int)tier );
1397                        else
1398                          printf( "  Active in tier #%d\n", (int)tier );
1399
1400                        if( !isBackup )
1401                        {
1402                            if( hasAnnounced )
1403                            {
1404                                tr_strltime( buf, now - lastAnnounceTime, sizeof( buf ) );
1405                                if( lastAnnounceSucceeded )
1406                                    printf( "  Got a list of %'d peers %s ago\n",
1407                                            (int)lastAnnouncePeerCount, buf );
1408                                else if( lastAnnounceTimedOut )
1409                                    printf( "  Peer list request timed out; will retry\n" );
1410                                else
1411                                    printf( "  Got an error \"%s\" %s ago\n",
1412                                            lastAnnounceResult, buf );
1413                            }
1414
1415                            switch( announceState )
1416                            {
1417                                case TR_TRACKER_INACTIVE:
1418                                    printf( "  No updates scheduled\n" );
1419                                    break;
1420                                case TR_TRACKER_WAITING:
1421                                    tr_strltime( buf, nextAnnounceTime - now, sizeof( buf ) );
1422                                    printf( "  Asking for more peers in %s\n", buf );
1423                                    break;
1424                                case TR_TRACKER_QUEUED:
1425                                    printf( "  Queued to ask for more peers\n" );
1426                                    break;
1427                                case TR_TRACKER_ACTIVE:
1428                                    tr_strltime( buf, now - lastAnnounceStartTime, sizeof( buf ) );
1429                                    printf( "  Asking for more peers now... %s\n", buf );
1430                                    break;
1431                            }
1432
1433                            if( hasScraped )
1434                            {
1435                                tr_strltime( buf, now - lastScrapeTime, sizeof( buf ) );
1436                                if( lastScrapeSucceeded )
1437                                    printf( "  Tracker had %'d seeders and %'d leechers %s ago\n",
1438                                            (int)seederCount, (int)leecherCount, buf );
1439                                else
1440                                    printf( "  Got a scrape error \"%s\" %s ago\n",
1441                                            lastScrapeResult, buf );
1442                            }
1443
1444                            switch( scrapeState )
1445                            {
1446                                case TR_TRACKER_INACTIVE:
1447                                    break;
1448                                case TR_TRACKER_WAITING:
1449                                    tr_strltime( buf, nextScrapeTime - now, sizeof( buf ) );
1450                                    printf( "  Asking for peer counts in %s\n", buf );
1451                                    break;
1452                                case TR_TRACKER_QUEUED:
1453                                    printf( "  Queued to ask for peer counts\n" );
1454                                    break;
1455                                case TR_TRACKER_ACTIVE:
1456                                    tr_strltime( buf, now - lastScrapeStartTime, sizeof( buf ) );
1457                                    printf( "  Asking for peer counts now... %s\n", buf );
1458                                    break;
1459                            }
1460                        }
1461                    }
1462                }
1463                printf( "\n" );
1464            }
1465
1466            printf( "ORIGINS\n" );
1467            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
1468            {
1469                const time_t tt = i;
1470                printf( "  Date created: %s", ctime( &tt ) );
1471            }
1472            if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) )
1473                printf( "  Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) );
1474            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
1475                printf( "  Comment: %s\n", str );
1476            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
1477                printf( "  Creator: %s\n", str );
1478            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
1479                printf( "  Piece Count: %" PRId64 "\n", i );
1480            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
1481                printf( "  Piece Size: %" PRId64 "\n", i );
1482            printf( "\n" );
1483
1484            printf( "LIMITS\n" );
1485            if( tr_bencDictFindBool( t, "downloadLimited", &boolVal )
1486                && tr_bencDictFindInt( t, "downloadLimit", &i ) )
1487            {
1488                printf( "  Download Limit: " );
1489                if( boolVal )
1490                    printf( "%" PRId64 " KB/s\n", i );
1491                else
1492                    printf( "Unlimited\n" );
1493            }
1494            if( tr_bencDictFindBool( t, "uploadLimited", &boolVal )
1495                && tr_bencDictFindInt( t, "uploadLimit", &i ) )
1496            {
1497                printf( "  Upload Limit: " );
1498                if( boolVal )
1499                    printf( "%" PRId64 " KB/s\n", i );
1500                else
1501                    printf( "Unlimited\n" );
1502            }
1503            if( tr_bencDictFindBool( t, "honorsSessionLimits", &boolVal ) )
1504                printf( "  Honors Session Limits: %s\n", ( boolVal ? "Yes" : "No" ) );
1505            if( tr_bencDictFindInt ( t, "peer-limit", &i ) )
1506                printf( "  Peer limit: %" PRId64 "\n", i );
1507            printf( "\n" );
1508
1509            printf( "PIECES\n" );
1510            if( tr_bencDictFindRaw( t, "pieces", &raw, &rawlen ) && tr_bencDictFindInt( t, "pieceCount", &j ) ) {
1511                int len;
1512                char * str = tr_base64_decode( raw, rawlen, &len );
1513                printf( "  " );
1514                for( i=k=0; k<len; ++k ) {
1515                    int e;
1516                    for( e=0; i<j && e<8; ++e, ++i )
1517                        printf( "%c", str[k] & (1<<(7-e)) ? '1' : '0' );
1518                    printf( " " );
1519                    if( !(i%64) )
1520                        printf( "\n  " );
1521                }
1522                tr_free( str );
1523            }
1524            printf( "\n" );
1525        }
1526    }
1527}
1528
1529static void
1530printFileList( tr_benc * top )
1531{
1532    tr_benc *args, *torrents;
1533
1534    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1535      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1536    {
1537        int i, in;
1538        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
1539        {
1540            tr_benc *    d = tr_bencListChild( torrents, i );
1541            tr_benc *    files, *priorities, *wanteds;
1542            const char * name;
1543            if( tr_bencDictFindStr( d, "name", &name )
1544              && tr_bencDictFindList( d, "files", &files )
1545              && tr_bencDictFindList( d, "priorities", &priorities )
1546              && tr_bencDictFindList( d, "wanted", &wanteds ) )
1547            {
1548                int j = 0, jn = tr_bencListSize( files );
1549                printf( "%s (%d files):\n", name, jn );
1550                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
1551                        "Priority", "Get", "Size",
1552                        "Name" );
1553                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1554                {
1555                    int64_t      have;
1556                    int64_t      length;
1557                    int64_t      priority;
1558                    int64_t      wanted;
1559                    const char * filename;
1560                    tr_benc *    file = tr_bencListChild( files, j );
1561                    if( tr_bencDictFindInt( file, "length", &length )
1562                      && tr_bencDictFindStr( file, "name", &filename )
1563                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
1564                      && tr_bencGetInt( tr_bencListChild( priorities,
1565                                                          j ), &priority )
1566                      && tr_bencGetInt( tr_bencListChild( wanteds,
1567                                                          j ), &wanted ) )
1568                    {
1569                        char         sizestr[64];
1570                        double       percent = (double)have / length;
1571                        const char * pristr;
1572                        strlsize( sizestr, length, sizeof( sizestr ) );
1573                        switch( priority )
1574                        {
1575                            case TR_PRI_LOW:
1576                                pristr = "Low"; break;
1577
1578                            case TR_PRI_HIGH:
1579                                pristr = "High"; break;
1580
1581                            default:
1582                                pristr = "Normal"; break;
1583                        }
1584                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
1585                                j,
1586                                floor( 100.0 * percent ),
1587                                pristr,
1588                                ( wanted ? "Yes" : "No" ),
1589                                sizestr,
1590                                filename );
1591                    }
1592                }
1593            }
1594        }
1595    }
1596}
1597
1598static void
1599printPeersImpl( tr_benc * peers )
1600{
1601    int i, n;
1602    printf( "%-20s  %-12s  %-5s %-6s  %-6s  %s\n",
1603            "Address", "Flags", "Done", "Down", "Up", "Client" );
1604    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1605    {
1606        double progress;
1607        const char * address, * client, * flagstr;
1608        int64_t rateToClient, rateToPeer;
1609        tr_benc * d = tr_bencListChild( peers, i );
1610
1611        if( tr_bencDictFindStr( d, "address", &address )
1612          && tr_bencDictFindStr( d, "clientName", &client )
1613          && tr_bencDictFindReal( d, "progress", &progress )
1614          && tr_bencDictFindStr( d, "flagStr", &flagstr )
1615          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1616          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1617        {
1618            printf( "%-20s  %-12s  %-5.1f %6.1f  %6.1f  %s\n",
1619                    address, flagstr, (progress*100.0),
1620                    rateToClient / 1024.0,
1621                    rateToPeer / 1024.0,
1622                    client );
1623        }
1624    }
1625}
1626
1627static void
1628printPeers( tr_benc * top )
1629{
1630    tr_benc *args, *torrents;
1631
1632    if( tr_bencDictFindDict( top, "arguments", &args )
1633      && tr_bencDictFindList( args, "torrents", &torrents ) )
1634    {
1635        int i, n;
1636        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1637        {
1638            tr_benc * peers;
1639            tr_benc * torrent = tr_bencListChild( torrents, i );
1640            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1641                printPeersImpl( peers );
1642                if( i+1<n )
1643                    printf( "\n" );
1644            }
1645        }
1646    }
1647}
1648
1649static void
1650printPortTest( tr_benc * top )
1651{
1652    tr_benc *args;
1653    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1654    {
1655        tr_bool      boolVal;
1656
1657        if( tr_bencDictFindBool( args, "port-is-open", &boolVal ) )
1658            printf( "Port is open: %s\n", ( boolVal ? "Yes" : "No" ) );
1659    }
1660}
1661
1662static void
1663printTorrentList( tr_benc * top )
1664{
1665    tr_benc *args, *list;
1666
1667    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1668      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1669    {
1670        int i, n;
1671        int64_t total_up = 0, total_down = 0, total_size = 0;
1672        char haveStr[32];
1673
1674        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1675                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1676                "Name" );
1677
1678        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1679        {
1680            int64_t      id, eta, status, up, down;
1681            int64_t      sizeWhenDone, leftUntilDone;
1682            double       ratio;
1683            const char * name;
1684            tr_benc *   d = tr_bencListChild( list, i );
1685            if( tr_bencDictFindInt( d, "eta", &eta )
1686              && tr_bencDictFindInt( d, "id", &id )
1687              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1688              && tr_bencDictFindStr( d, "name", &name )
1689              && tr_bencDictFindInt( d, "rateDownload", &down )
1690              && tr_bencDictFindInt( d, "rateUpload", &up )
1691              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1692              && tr_bencDictFindInt( d, "status", &status )
1693              && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1694            {
1695                char etaStr[16];
1696                char statusStr[64];
1697                char ratioStr[32];
1698                char doneStr[8];
1699                int64_t error;
1700                char errorMark;
1701
1702                if( sizeWhenDone )
1703                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1704                else
1705                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1706
1707                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1708
1709                if( leftUntilDone || eta != -1 )
1710                    etaToString( etaStr, sizeof( etaStr ), eta );
1711                else
1712                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1713                if( tr_bencDictFindInt( d, "error", &error ) && error )
1714                    errorMark = '*';
1715                else
1716                    errorMark = ' ';
1717                printf(
1718                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1719                    (int)id, errorMark,
1720                    doneStr,
1721                    haveStr,
1722                    etaStr,
1723                    up / 1024.0,
1724                    down / 1024.0,
1725                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1726                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1727                    name );
1728
1729                total_up += up;
1730                total_down += down;
1731                total_size += sizeWhenDone - leftUntilDone;
1732            }
1733        }
1734
1735        printf( "Sum:         %9s            %6.1f  %6.1f\n",
1736                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1737                total_up / 1024.0,
1738                total_down / 1024.0 );
1739    }
1740}
1741
1742static int
1743processResponse( const char * host,
1744                 int          port,
1745                 const void * response,
1746                 size_t       len )
1747{
1748    tr_benc top;
1749    int status = EXIT_SUCCESS;
1750
1751    if( debug )
1752        fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1753                 (int)len, (int)len, (int)len, (const char*) response );
1754
1755    if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1756    {
1757        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1758                 (int)len, (char*)response );
1759        status |= EXIT_FAILURE;
1760    }
1761    else
1762    {
1763        int64_t      tag = -1;
1764        const char * str;
1765        tr_bencDictFindInt( &top, "tag", &tag );
1766
1767        switch( tag )
1768        {
1769            case TAG_SESSION:
1770                printSession( &top ); break;
1771
1772            case TAG_STATS:
1773                printSessionStats( &top ); break;
1774
1775            case TAG_FILES:
1776                printFileList( &top ); break;
1777
1778            case TAG_DETAILS:
1779                printDetails( &top ); break;
1780
1781            case TAG_LIST:
1782                printTorrentList( &top ); break;
1783
1784            case TAG_PEERS:
1785                printPeers( &top ); break;
1786
1787            case TAG_PORTTEST:
1788                printPortTest( &top ); break;
1789
1790            default:
1791                if( !tr_bencDictFindStr( &top, "result", &str ) )
1792                    status |= EXIT_FAILURE;
1793                else {
1794                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1795                    if( strcmp( str, "success") )
1796                        status |= EXIT_FAILURE;
1797                }
1798        }
1799
1800        tr_bencFree( &top );
1801    }
1802
1803    return status;
1804}
1805
1806/* look for a session id in the header in case the server gives back a 409 */
1807static size_t
1808parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
1809{
1810    const char * line = ptr;
1811    const size_t line_len = size * nmemb;
1812    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
1813    const size_t key_len = strlen( key );
1814
1815    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
1816    {
1817        const char * begin = line + key_len;
1818        const char * end = begin;
1819        while( !isspace( *end ) )
1820            ++end;
1821        tr_free( sessionId );
1822        sessionId = tr_strndup( begin, end-begin );
1823    }
1824
1825    return line_len;
1826}
1827
1828static CURL*
1829tr_curl_easy_init( struct evbuffer * writebuf )
1830{
1831    CURL * curl = curl_easy_init( );
1832    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1833    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1834    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1835    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1836    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1837    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1838    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1839    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
1840    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1841    curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1842    if( netrc )
1843        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1844    if( auth )
1845        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1846    if( sessionId ) {
1847        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1848        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1849        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1850        /* fixme: leaks */
1851    }
1852    return curl;
1853}
1854
1855
1856static int
1857processRequests( const char *  host,
1858                 int           port,
1859                 const char ** reqs,
1860                 int           reqCount )
1861{
1862    int i;
1863    CURL * curl = NULL;
1864    struct evbuffer * buf = evbuffer_new( );
1865    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1866    int status = EXIT_SUCCESS;
1867
1868    for( i=0; i<reqCount; ++i )
1869    {
1870        CURLcode res;
1871        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1872
1873        if( curl == NULL )
1874        {
1875            curl = tr_curl_easy_init( buf );
1876            curl_easy_setopt( curl, CURLOPT_URL, url );
1877        }
1878
1879        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1880
1881        if( debug )
1882            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
1883        if( ( res = curl_easy_perform( curl ) ) )
1884        {
1885            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
1886            status |= EXIT_FAILURE;
1887        }
1888        else {
1889            long response;
1890            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1891            switch( response ) {
1892                case 200:
1893                    status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1894                    break;
1895                case 409:
1896                    /* session id failed.  our curl header func has already
1897                     * pulled the new session id from this response's headers,
1898                     * build a new CURL* and try again */
1899                    curl_easy_cleanup( curl );
1900                    curl = NULL;
1901                    --i;
1902                    break;
1903                default:
1904                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1905                    status |= EXIT_FAILURE;
1906                    break;
1907            }
1908        }
1909    }
1910
1911    /* cleanup */
1912    tr_free( url );
1913    evbuffer_free( buf );
1914    if( curl != NULL )
1915        curl_easy_cleanup( curl );
1916    return status;
1917}
1918
1919int
1920main( int     argc,
1921      char ** argv )
1922{
1923    int    i;
1924    int    port = DEFAULT_PORT;
1925    char * host = NULL;
1926    int    exit_status = EXIT_SUCCESS;
1927
1928    if( argc < 2 ) {
1929        showUsage( );
1930        return EXIT_FAILURE;
1931    }
1932
1933    getHostAndPort( &argc, argv, &host, &port );
1934    if( host == NULL )
1935        host = tr_strdup( DEFAULT_HOST );
1936
1937    readargs( argc, (const char**)argv );
1938    if( reqCount == 0 ) {
1939        showUsage( );
1940        return EXIT_FAILURE;
1941    }
1942
1943    exit_status = processRequests( host, port, (const char**)reqs, reqCount );
1944
1945    for( i=0; i<reqCount; ++i )
1946        tr_free( reqs[i] );
1947
1948    tr_free( host );
1949    return exit_status;
1950}
1951
Note: See TracBrowser for help on using the repository browser.