source: trunk/daemon/remote.c @ 10536

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

(trunk) #3174 "Use IEC standard units (KiB, MiB, GiB) instead of (KB, MB, GB)" -- implemented for gtk, qt, daemon, cli, and web client

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