source: trunk/daemon/remote.c @ 9259

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

(trunk) replace tr_tracker_stat's "isActive" field with the more descriptive "isBackup"

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