source: trunk/daemon/remote.c @ 10099

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

(trunk) add a new field to distinguish from error messages returned from the tracker, and announce timeouts, so that they can be displayed differently

  • Property svn:keywords set to Date Rev Author Id
File size: 70.1 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 10099 2010-02-05 05:16:18Z 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( "CONFIG\n" );
1057        if( tr_bencDictFindStr( args, "config-dir", &str ) )
1058            printf( "  Configuration directory: %s\n", str );
1059        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
1060            printf( "  Download directory: %s\n", str );
1061        if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) )
1062            printf( "  Listenport: %" PRId64 "\n", i );
1063        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
1064            printf( "  Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1065        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
1066            printf( "  Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) );
1067        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_ENCRYPTION, &str ) )
1068            printf( "  Encryption: %s\n", str );
1069        printf( "\n" );
1070
1071        {
1072            tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled;
1073            int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit;
1074
1075            if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN, &altDown ) &&
1076                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) &&
1077                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) &&
1078                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) &&
1079                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) &&
1080                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) &&
1081                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP, &altUp ) &&
1082                tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) &&
1083                tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED, &downLimit ) &&
1084                tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) &&
1085                tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED, &upLimit ) &&
1086                tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) )
1087            {
1088                char buf[128];
1089
1090                printf( "LIMITS\n" );
1091                printf( "  Peer limit: %" PRId64 "\n", peerLimit );
1092
1093                if( altEnabled )
1094                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altUp );
1095                else if( upEnabled )
1096                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", upLimit );
1097                else
1098                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1099                printf( "  Upload speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
1100                        buf,
1101                        (upEnabled?"Enabled":"Disabled"), upLimit,
1102                        (altEnabled?"Enabled":"Disabled"), altUp );
1103
1104                if( altEnabled )
1105                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altDown );
1106                else if( downEnabled )
1107                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", downLimit );
1108                else
1109                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1110                printf( "  Download speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
1111                        buf,
1112                        (downEnabled?"Enabled":"Disabled"), downLimit,
1113                        (altEnabled?"Enabled":"Disabled"), altDown );
1114
1115                if( altTimeEnabled ) {
1116                    printf( "  Turtle schedule: %02d:%02d - %02d:%02d  ",
1117                            (int)(altBegin/60), (int)(altBegin%60),
1118                            (int)(altEnd/60), (int)(altEnd%60) );
1119                    if( altDay & TR_SCHED_SUN )   printf( "Sun " );
1120                    if( altDay & TR_SCHED_MON )   printf( "Mon " );
1121                    if( altDay & TR_SCHED_TUES )  printf( "Tue " );
1122                    if( altDay & TR_SCHED_WED )   printf( "Wed " );
1123                    if( altDay & TR_SCHED_THURS ) printf( "Thu " );
1124                    if( altDay & TR_SCHED_FRI )   printf( "Fri " );
1125                    if( altDay & TR_SCHED_SAT )   printf( "Sat " );
1126                    printf( "\n" );
1127                }
1128            }
1129        }
1130    }
1131}
1132
1133static void
1134printSessionStats( tr_benc * top )
1135{
1136    tr_benc *args, *d;
1137    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1138    {
1139        char buf[512];
1140        int64_t up, down, secs, sessions;
1141
1142        if( tr_bencDictFindDict( args, "current-stats", &d )
1143            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1144            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1145            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1146        {
1147            printf( "\nCURRENT SESSION\n" );
1148            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1149            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1150            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1151            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1152        }
1153
1154        if( tr_bencDictFindDict( args, "cumulative-stats", &d )
1155            && tr_bencDictFindInt( d, "sessionCount", &sessions )
1156            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1157            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1158            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1159        {
1160            printf( "\nTOTAL\n" );
1161            printf( "  Started %lu times\n", (unsigned long)sessions );
1162            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1163            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1164            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1165            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1166        }
1167    }
1168}
1169
1170static void
1171printDetails( tr_benc * top )
1172{
1173    tr_benc *args, *torrents;
1174
1175    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1176      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1177    {
1178        int ti, tCount;
1179        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
1180             ++ti )
1181        {
1182            tr_benc *    t = tr_bencListChild( torrents, ti );
1183            tr_benc *    l;
1184            const uint8_t * raw;
1185            size_t       rawlen;
1186            const char * str;
1187            char         buf[512];
1188            char         buf2[512];
1189            int64_t      i, j, k;
1190            tr_bool      boolVal;
1191
1192            printf( "NAME\n" );
1193            if( tr_bencDictFindInt( t, "id", &i ) )
1194                printf( "  Id: %" PRId64 "\n", i );
1195            if( tr_bencDictFindStr( t, "name", &str ) )
1196                printf( "  Name: %s\n", str );
1197            if( tr_bencDictFindStr( t, "hashString", &str ) )
1198                printf( "  Hash: %s\n", str );
1199            printf( "\n" );
1200
1201            printf( "TRANSFER\n" );
1202            getStatusString( t, buf, sizeof( buf ) );
1203            printf( "  State: %s\n", buf );
1204
1205            if( tr_bencDictFindStr( t, "downloadDir", &str ) )
1206                printf( "  Location: %s\n", str );
1207
1208            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1209              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
1210            {
1211                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
1212                printf( "  Percent Done: %s%%\n", buf );
1213            }
1214
1215            if( tr_bencDictFindInt( t, "eta", &i ) )
1216                printf( "  ETA: %s\n", tr_strltime( buf, i, sizeof( buf ) ) );
1217            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
1218                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
1219            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
1220                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
1221            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
1222              && tr_bencDictFindInt( t, "haveValid", &j ) )
1223            {
1224                strlsize( buf, i + j, sizeof( buf ) );
1225                strlsize( buf2, j, sizeof( buf2 ) );
1226                printf( "  Have: %s (%s verified)\n", buf, buf2 );
1227            }
1228
1229            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1230              && tr_bencDictFindInt( t, "totalSize", &j ) )
1231            {
1232                strlsize( buf, j, sizeof( buf ) );
1233                strlsize( buf2, i, sizeof( buf2 ) );
1234                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
1235            }
1236            if( tr_bencDictFindInt( t, "downloadedEver", &i )
1237              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
1238            {
1239                strlsize( buf, i, sizeof( buf ) );
1240                printf( "  Downloaded: %s\n", buf );
1241                strlsize( buf, j, sizeof( buf ) );
1242                printf( "  Uploaded: %s\n", buf );
1243                strlratio( buf, j, i, sizeof( buf ) );
1244                printf( "  Ratio: %s\n", buf );
1245            }
1246            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
1247            {
1248                strlsize( buf, i, sizeof( buf ) );
1249                printf( "  Corrupt DL: %s\n", buf );
1250            }
1251            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str &&
1252                tr_bencDictFindInt( t, "error", &i ) && i )
1253            {
1254                switch( i ) {
1255                    case TR_STAT_TRACKER_WARNING: printf( "  Tracker gave a warning: %s\n", str ); break;
1256                    case TR_STAT_TRACKER_ERROR:   printf( "  Tracker gave an error: %s\n", str ); break;
1257                    case TR_STAT_LOCAL_ERROR:     printf( "  Error: %s\n", str ); break;
1258                    default: break; /* no error */
1259                }
1260            }
1261            if( tr_bencDictFindInt( t, "peersConnected", &i )
1262              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
1263              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
1264            {
1265                printf(
1266                    "  Peers: "
1267                    "connected to %" PRId64 ", "
1268                                            "uploading to %" PRId64
1269                    ", "
1270                    "downloading from %"
1271                    PRId64 "\n",
1272                    i, j, k );
1273            }
1274
1275            if( tr_bencDictFindList( t, "webseeds", &l )
1276              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
1277            {
1278                const int64_t n = tr_bencListSize( l );
1279                if( n > 0 )
1280                    printf(
1281                        "  Web Seeds: downloading from %" PRId64 " of %"
1282                        PRId64
1283                        " web seeds\n", i, n );
1284            }
1285            printf( "\n" );
1286
1287            printf( "HISTORY\n" );
1288            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
1289            {
1290                const time_t tt = i;
1291                printf( "  Date added:      %s", ctime( &tt ) );
1292            }
1293            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
1294            {
1295                const time_t tt = i;
1296                printf( "  Date finished:   %s", ctime( &tt ) );
1297            }
1298            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
1299            {
1300                const time_t tt = i;
1301                printf( "  Date started:    %s", ctime( &tt ) );
1302            }
1303            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
1304            {
1305                const time_t tt = i;
1306                printf( "  Latest activity: %s", ctime( &tt ) );
1307            }
1308            printf( "\n" );
1309
1310            printf( "TRACKERS\n" );
1311
1312            if( tr_bencDictFindList( t, "trackerStats", &l ) )
1313            {
1314                tr_benc * t;
1315                for( i=0; (( t = tr_bencListChild( l, i ))); ++i )
1316                {
1317                    int64_t downloadCount;
1318                    tr_bool hasAnnounced;
1319                    tr_bool hasScraped;
1320                    const char * host;
1321                    tr_bool isBackup;
1322                    int64_t lastAnnouncePeerCount;
1323                    const char * lastAnnounceResult;
1324                    int64_t lastAnnounceStartTime;
1325                    tr_bool lastAnnounceSucceeded;
1326                    int64_t lastAnnounceTime;
1327                    tr_bool lastAnnounceTimedOut;
1328                    const char * lastScrapeResult;
1329                    tr_bool lastScrapeSucceeded;
1330                    int64_t lastScrapeStartTime;
1331                    int64_t lastScrapeTime;
1332                    int64_t leecherCount;
1333                    int64_t nextAnnounceTime;
1334                    int64_t nextScrapeTime;
1335                    int64_t seederCount;
1336                    int64_t tier;
1337                    int64_t announceState;
1338                    int64_t scrapeState;
1339
1340                    if( tr_bencDictFindInt ( t, "downloadCount", &downloadCount ) &&
1341                        tr_bencDictFindBool( t, "hasAnnounced", &hasAnnounced ) &&
1342                        tr_bencDictFindBool( t, "hasScraped", &hasScraped ) &&
1343                        tr_bencDictFindStr ( t, "host", &host ) &&
1344                        tr_bencDictFindBool( t, "isBackup", &isBackup ) &&
1345                        tr_bencDictFindInt ( t, "announceState", &announceState ) &&
1346                        tr_bencDictFindInt ( t, "scrapeState", &scrapeState ) &&
1347                        tr_bencDictFindInt ( t, "lastAnnouncePeerCount", &lastAnnouncePeerCount ) &&
1348                        tr_bencDictFindStr ( t, "lastAnnounceResult", &lastAnnounceResult ) &&
1349                        tr_bencDictFindInt ( t, "lastAnnounceStartTime", &lastAnnounceStartTime ) &&
1350                        tr_bencDictFindBool( t, "lastAnnounceSucceeded", &lastAnnounceSucceeded ) &&
1351                        tr_bencDictFindInt ( t, "lastAnnounceTime", &lastAnnounceTime ) &&
1352                        tr_bencDictFindBool( t, "lastAnnounceTimedOut", &lastAnnounceTimedOut ) &&
1353                        tr_bencDictFindStr ( t, "lastScrapeResult", &lastScrapeResult ) &&
1354                        tr_bencDictFindInt ( t, "lastScrapeStartTime", &lastScrapeStartTime ) &&
1355                        tr_bencDictFindBool( t, "lastScrapeSucceeded", &lastScrapeSucceeded ) &&
1356                        tr_bencDictFindInt ( t, "lastScrapeTime", &lastScrapeTime ) &&
1357                        tr_bencDictFindInt ( t, "leecherCount", &leecherCount ) &&
1358                        tr_bencDictFindInt ( t, "nextAnnounceTime", &nextAnnounceTime ) &&
1359                        tr_bencDictFindInt ( t, "nextScrapeTime", &nextScrapeTime ) &&
1360                        tr_bencDictFindInt ( t, "seederCount", &seederCount ) &&
1361                        tr_bencDictFindInt ( t, "tier", &tier ) )
1362                    {
1363                        const time_t now = time( NULL );
1364
1365                        printf( "\n" );
1366                        printf( "  Tracker #%d: %s\n", (int)(i+1), host );
1367                        if( isBackup )
1368                          printf( "  Backup on tier #%d\n", (int)tier );
1369                        else
1370                          printf( "  Active in tier #%d\n", (int)tier );
1371
1372                        if( !isBackup )
1373                        {
1374                            if( hasAnnounced )
1375                            {
1376                                tr_strltime( buf, now - lastAnnounceTime, sizeof( buf ) );
1377                                if( lastAnnounceSucceeded )
1378                                    printf( "  Got a list of %'d peers %s ago\n",
1379                                            (int)lastAnnouncePeerCount, buf );
1380                                else if( lastAnnounceTimedOut )
1381                                    printf( "  Peer list request timed out; will retry\n" );
1382                                else
1383                                    printf( "  Got an error \"%s\" %s ago\n",
1384                                            lastAnnounceResult, buf );
1385                            }
1386
1387                            switch( announceState )
1388                            {
1389                                case TR_TRACKER_INACTIVE:
1390                                    printf( "  No updates scheduled\n" );
1391                                    break;
1392                                case TR_TRACKER_WAITING:
1393                                    tr_strltime( buf, nextAnnounceTime - now, sizeof( buf ) );
1394                                    printf( "  Asking for more peers in %s\n", buf );
1395                                    break;
1396                                case TR_TRACKER_QUEUED:
1397                                    printf( "  Queued to ask for more peers\n" );
1398                                    break;
1399                                case TR_TRACKER_ACTIVE:
1400                                    tr_strltime( buf, now - lastAnnounceStartTime, sizeof( buf ) );
1401                                    printf( "  Asking for more peers now... %s\n", buf );
1402                                    break;
1403                            }
1404
1405                            if( hasScraped )
1406                            {
1407                                tr_strltime( buf, now - lastScrapeTime, sizeof( buf ) );
1408                                if( lastScrapeSucceeded )
1409                                    printf( "  Tracker had %'d seeders and %'d leechers %s ago\n",
1410                                            (int)seederCount, (int)leecherCount, buf );
1411                                else
1412                                    printf( "  Got a scrape error \"%s\" %s ago\n",
1413                                            lastScrapeResult, buf );
1414                            }
1415
1416                            switch( scrapeState )
1417                            {
1418                                case TR_TRACKER_INACTIVE:
1419                                    break;
1420                                case TR_TRACKER_WAITING:
1421                                    tr_strltime( buf, nextScrapeTime - now, sizeof( buf ) );
1422                                    printf( "  Asking for peer counts in %s\n", buf );
1423                                    break;
1424                                case TR_TRACKER_QUEUED:
1425                                    printf( "  Queued to ask for peer counts\n" );
1426                                    break;
1427                                case TR_TRACKER_ACTIVE:
1428                                    tr_strltime( buf, now - lastScrapeStartTime, sizeof( buf ) );
1429                                    printf( "  Asking for peer counts now... %s\n", buf );
1430                                    break;
1431                            }
1432                        }
1433                    }
1434                }
1435                printf( "\n" );
1436            }
1437
1438            printf( "ORIGINS\n" );
1439            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
1440            {
1441                const time_t tt = i;
1442                printf( "  Date created: %s", ctime( &tt ) );
1443            }
1444            if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) )
1445                printf( "  Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) );
1446            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
1447                printf( "  Comment: %s\n", str );
1448            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
1449                printf( "  Creator: %s\n", str );
1450            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
1451                printf( "  Piece Count: %" PRId64 "\n", i );
1452            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
1453                printf( "  Piece Size: %" PRId64 "\n", i );
1454            printf( "\n" );
1455
1456            printf( "LIMITS\n" );
1457            if( tr_bencDictFindBool( t, "downloadLimited", &boolVal )
1458                && tr_bencDictFindInt( t, "downloadLimit", &i ) )
1459            {
1460                printf( "  Download Limit: " );
1461                if( boolVal )
1462                    printf( "%" PRId64 " KB/s\n", i );
1463                else
1464                    printf( "Unlimited\n" );
1465            }
1466            if( tr_bencDictFindBool( t, "uploadLimited", &boolVal )
1467                && tr_bencDictFindInt( t, "uploadLimit", &i ) )
1468            {
1469                printf( "  Upload Limit: " );
1470                if( boolVal )
1471                    printf( "%" PRId64 " KB/s\n", i );
1472                else
1473                    printf( "Unlimited\n" );
1474            }
1475            if( tr_bencDictFindBool( t, "honorsSessionLimits", &boolVal ) )
1476                printf( "  Honors Session Limits: %s\n", ( boolVal ? "Yes" : "No" ) );
1477            if( tr_bencDictFindInt ( t, "peer-limit", &i ) )
1478                printf( "  Peer limit: %" PRId64 "\n", i );
1479            printf( "\n" );
1480
1481            printf( "PIECES\n" );
1482            if( tr_bencDictFindRaw( t, "pieces", &raw, &rawlen ) && tr_bencDictFindInt( t, "pieceCount", &j ) ) {
1483                int len;
1484                char * str = tr_base64_decode( raw, rawlen, &len );
1485                printf( "  " );
1486                for( i=k=0; k<len; ++k ) {
1487                    int e;
1488                    for( e=0; i<j && e<8; ++e, ++i )
1489                        printf( "%c", str[k] & (1<<(7-e)) ? '1' : '0' );
1490                    printf( " " );
1491                    if( !(i%64) )
1492                        printf( "\n  " );
1493                }
1494                tr_free( str );
1495            }
1496            printf( "\n" );
1497        }
1498    }
1499}
1500
1501static void
1502printFileList( tr_benc * top )
1503{
1504    tr_benc *args, *torrents;
1505
1506    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1507      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1508    {
1509        int i, in;
1510        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
1511        {
1512            tr_benc *    d = tr_bencListChild( torrents, i );
1513            tr_benc *    files, *priorities, *wanteds;
1514            const char * name;
1515            if( tr_bencDictFindStr( d, "name", &name )
1516              && tr_bencDictFindList( d, "files", &files )
1517              && tr_bencDictFindList( d, "priorities", &priorities )
1518              && tr_bencDictFindList( d, "wanted", &wanteds ) )
1519            {
1520                int j = 0, jn = tr_bencListSize( files );
1521                printf( "%s (%d files):\n", name, jn );
1522                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
1523                        "Priority", "Get", "Size",
1524                        "Name" );
1525                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1526                {
1527                    int64_t      have;
1528                    int64_t      length;
1529                    int64_t      priority;
1530                    int64_t      wanted;
1531                    const char * filename;
1532                    tr_benc *    file = tr_bencListChild( files, j );
1533                    if( tr_bencDictFindInt( file, "length", &length )
1534                      && tr_bencDictFindStr( file, "name", &filename )
1535                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
1536                      && tr_bencGetInt( tr_bencListChild( priorities,
1537                                                          j ), &priority )
1538                      && tr_bencGetInt( tr_bencListChild( wanteds,
1539                                                          j ), &wanted ) )
1540                    {
1541                        char         sizestr[64];
1542                        double       percent = (double)have / length;
1543                        const char * pristr;
1544                        strlsize( sizestr, length, sizeof( sizestr ) );
1545                        switch( priority )
1546                        {
1547                            case TR_PRI_LOW:
1548                                pristr = "Low"; break;
1549
1550                            case TR_PRI_HIGH:
1551                                pristr = "High"; break;
1552
1553                            default:
1554                                pristr = "Normal"; break;
1555                        }
1556                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
1557                                j,
1558                                floor( 100.0 * percent ),
1559                                pristr,
1560                                ( wanted ? "Yes" : "No" ),
1561                                sizestr,
1562                                filename );
1563                    }
1564                }
1565            }
1566        }
1567    }
1568}
1569
1570static void
1571printPeersImpl( tr_benc * peers )
1572{
1573    int i, n;
1574    printf( "%-20s  %-12s  %-5s %-6s  %-6s  %s\n",
1575            "Address", "Flags", "Done", "Down", "Up", "Client" );
1576    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1577    {
1578        double progress;
1579        const char * address, * client, * flagstr;
1580        int64_t rateToClient, rateToPeer;
1581        tr_benc * d = tr_bencListChild( peers, i );
1582
1583        if( tr_bencDictFindStr( d, "address", &address )
1584          && tr_bencDictFindStr( d, "clientName", &client )
1585          && tr_bencDictFindReal( d, "progress", &progress )
1586          && tr_bencDictFindStr( d, "flagStr", &flagstr )
1587          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1588          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1589        {
1590            printf( "%-20s  %-12s  %-5.1f %6.1f  %6.1f  %s\n",
1591                    address, flagstr, (progress*100.0),
1592                    rateToClient / 1024.0,
1593                    rateToPeer / 1024.0,
1594                    client );
1595        }
1596    }
1597}
1598
1599static void
1600printPeers( tr_benc * top )
1601{
1602    tr_benc *args, *torrents;
1603
1604    if( tr_bencDictFindDict( top, "arguments", &args )
1605      && tr_bencDictFindList( args, "torrents", &torrents ) )
1606    {
1607        int i, n;
1608        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1609        {
1610            tr_benc * peers;
1611            tr_benc * torrent = tr_bencListChild( torrents, i );
1612            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1613                printPeersImpl( peers );
1614                if( i+1<n )
1615                    printf( "\n" );
1616            }
1617        }
1618    }
1619}
1620
1621static void
1622printPortTest( tr_benc * top )
1623{
1624    tr_benc *args;
1625    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1626    {
1627        tr_bool      boolVal;
1628
1629        if( tr_bencDictFindBool( args, "port-is-open", &boolVal ) )
1630            printf( "Port is open: %s\n", ( boolVal ? "Yes" : "No" ) );
1631    }
1632}
1633
1634static void
1635printTorrentList( tr_benc * top )
1636{
1637    tr_benc *args, *list;
1638
1639    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1640      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1641    {
1642        int i, n;
1643        int64_t total_up = 0, total_down = 0, total_size = 0;
1644        char haveStr[32];
1645
1646        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1647                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1648                "Name" );
1649
1650        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1651        {
1652            int64_t      id, eta, status, up, down;
1653            int64_t      sizeWhenDone, leftUntilDone;
1654            double       ratio;
1655            const char * name;
1656            tr_benc *   d = tr_bencListChild( list, i );
1657            if( tr_bencDictFindInt( d, "eta", &eta )
1658              && tr_bencDictFindInt( d, "id", &id )
1659              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1660              && tr_bencDictFindStr( d, "name", &name )
1661              && tr_bencDictFindInt( d, "rateDownload", &down )
1662              && tr_bencDictFindInt( d, "rateUpload", &up )
1663              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1664              && tr_bencDictFindInt( d, "status", &status )
1665              && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1666            {
1667                char etaStr[16];
1668                char statusStr[64];
1669                char ratioStr[32];
1670                char doneStr[8];
1671                int64_t error;
1672                char errorMark;
1673
1674                if( sizeWhenDone )
1675                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1676                else
1677                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1678
1679                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1680
1681                if( leftUntilDone || eta != -1 )
1682                    etaToString( etaStr, sizeof( etaStr ), eta );
1683                else
1684                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1685                if( tr_bencDictFindInt( d, "error", &error ) && error )
1686                    errorMark = '*';
1687                else
1688                    errorMark = ' ';
1689                printf(
1690                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1691                    (int)id, errorMark,
1692                    doneStr,
1693                    haveStr,
1694                    etaStr,
1695                    up / 1024.0,
1696                    down / 1024.0,
1697                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1698                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1699                    name );
1700
1701                total_up += up;
1702                total_down += down;
1703                total_size += sizeWhenDone - leftUntilDone;
1704            }
1705        }
1706
1707        printf( "Sum:         %9s            %6.1f  %6.1f\n",
1708                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1709                total_up / 1024.0,
1710                total_down / 1024.0 );
1711    }
1712}
1713
1714static int
1715processResponse( const char * host,
1716                 int          port,
1717                 const void * response,
1718                 size_t       len )
1719{
1720    tr_benc top;
1721    int status = EXIT_SUCCESS;
1722
1723    if( debug )
1724        fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1725                 (int)len, (int)len, (int)len, (const char*) response );
1726
1727    if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1728    {
1729        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1730                 (int)len, (char*)response );
1731        status |= EXIT_FAILURE;
1732    }
1733    else
1734    {
1735        int64_t      tag = -1;
1736        const char * str;
1737        tr_bencDictFindInt( &top, "tag", &tag );
1738
1739        switch( tag )
1740        {
1741            case TAG_SESSION:
1742                printSession( &top ); break;
1743
1744            case TAG_STATS:
1745                printSessionStats( &top ); break;
1746
1747            case TAG_FILES:
1748                printFileList( &top ); break;
1749
1750            case TAG_DETAILS:
1751                printDetails( &top ); break;
1752
1753            case TAG_LIST:
1754                printTorrentList( &top ); break;
1755
1756            case TAG_PEERS:
1757                printPeers( &top ); break;
1758
1759            case TAG_PORTTEST:
1760                printPortTest( &top ); break;
1761
1762            default:
1763                if( !tr_bencDictFindStr( &top, "result", &str ) )
1764                    status |= EXIT_FAILURE;
1765                else {
1766                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1767                    if( strcmp( str, "success") )
1768                        status |= EXIT_FAILURE;
1769                }
1770        }
1771
1772        tr_bencFree( &top );
1773    }
1774
1775    return status;
1776}
1777
1778/* look for a session id in the header in case the server gives back a 409 */
1779static size_t
1780parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
1781{
1782    const char * line = ptr;
1783    const size_t line_len = size * nmemb;
1784    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
1785    const size_t key_len = strlen( key );
1786
1787    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
1788    {
1789        const char * begin = line + key_len;
1790        const char * end = begin;
1791        while( !isspace( *end ) )
1792            ++end;
1793        tr_free( sessionId );
1794        sessionId = tr_strndup( begin, end-begin );
1795    }
1796
1797    return line_len;
1798}
1799
1800static CURL*
1801tr_curl_easy_init( struct evbuffer * writebuf )
1802{
1803    CURL * curl = curl_easy_init( );
1804    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1805    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1806    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1807    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1808    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1809    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1810    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1811    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
1812    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1813    curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1814    if( netrc )
1815        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1816    if( auth )
1817        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1818    if( sessionId ) {
1819        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1820        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1821        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1822        /* fixme: leaks */
1823    }
1824    return curl;
1825}
1826
1827
1828static int
1829processRequests( const char *  host,
1830                 int           port,
1831                 const char ** reqs,
1832                 int           reqCount )
1833{
1834    int i;
1835    CURL * curl = NULL;
1836    struct evbuffer * buf = evbuffer_new( );
1837    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1838    int status = EXIT_SUCCESS;
1839
1840    for( i=0; i<reqCount; ++i )
1841    {
1842        CURLcode res;
1843        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1844
1845        if( curl == NULL )
1846        {
1847            curl = tr_curl_easy_init( buf );
1848            curl_easy_setopt( curl, CURLOPT_URL, url );
1849        }
1850
1851        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1852
1853        if( debug )
1854            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
1855        if( ( res = curl_easy_perform( curl ) ) )
1856        {
1857            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
1858            status |= EXIT_FAILURE;
1859        }
1860        else {
1861            long response;
1862            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1863            switch( response ) {
1864                case 200:
1865                    status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1866                    break;
1867                case 409:
1868                    /* session id failed.  our curl header func has already
1869                     * pulled the new session id from this response's headers,
1870                     * build a new CURL* and try again */
1871                    curl_easy_cleanup( curl );
1872                    curl = NULL;
1873                    --i;
1874                    break;
1875                default:
1876                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1877                    status |= EXIT_FAILURE;
1878                    break;
1879            }
1880        }
1881    }
1882
1883    /* cleanup */
1884    tr_free( url );
1885    evbuffer_free( buf );
1886    if( curl != NULL )
1887        curl_easy_cleanup( curl );
1888    return status;
1889}
1890
1891int
1892main( int     argc,
1893      char ** argv )
1894{
1895    int    i;
1896    int    port = DEFAULT_PORT;
1897    char * host = NULL;
1898    int    exit_status = EXIT_SUCCESS;
1899
1900    if( argc < 2 ) {
1901        showUsage( );
1902        return EXIT_FAILURE;
1903    }
1904
1905    getHostAndPort( &argc, argv, &host, &port );
1906    if( host == NULL )
1907        host = tr_strdup( DEFAULT_HOST );
1908
1909    readargs( argc, (const char**)argv );
1910    if( reqCount == 0 ) {
1911        showUsage( );
1912        return EXIT_FAILURE;
1913    }
1914
1915    exit_status = processRequests( host, port, (const char**)reqs, reqCount );
1916
1917    for( i=0; i<reqCount; ++i )
1918        tr_free( reqs[i] );
1919
1920    tr_free( host );
1921    return exit_status;
1922}
1923
Note: See TracBrowser for help on using the repository browser.