source: trunk/daemon/remote.c @ 9890

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

(trunk) one of the less-interesting commits in a while: remove trailing spaces from lines

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