source: trunk/daemon/remote.c @ 9511

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

(trunk daemon) #2155: typo fix: honnor -> honor

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