source: trunk/daemon/remote.c @ 10043

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

(trunk daemon) #2786 "Trigger blocklist update using transmission-remote" -- patch applied for 1.90

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