source: trunk/daemon/remote.c @ 8961

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

(trunk daemon) #2158: transmission-remote should support "turtle mode"

  • Property svn:keywords set to Date Rev Author Id
File size: 59.7 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 8961 2009-08-17 20:41:06Z 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    "announceResponse",
310    "announceURL",
311    "comment",
312    "corruptEver",
313    "creator",
314    "dateCreated",
315    "doneDate",
316    "downloadDir",
317    "downloadedEver",
318    "error",
319    "errorString",
320    "eta",
321    "hashString",
322    "haveUnchecked",
323    "haveValid",
324    "id",
325    "isPrivate",
326    "lastAnnounceTime",
327    "lastScrapeTime",
328    "leechers",
329    "leftUntilDone",
330    "name",
331    "nextAnnounceTime",
332    "nextScrapeTime",
333    "peersConnected",
334    "peersGettingFromUs",
335    "peersSendingToUs",
336    "pieceCount",
337    "pieceSize",
338    "rateDownload",
339    "rateUpload",
340    "recheckProgress",
341    "scrapeResponse",
342    "seeders",
343    "sizeWhenDone",
344    "startDate",
345    "status",
346    "timesCompleted",
347    "totalSize",
348    "uploadedEver",
349    "pieces",
350    "webseeds",
351    "webseedsSendingToUs"
352};
353
354static const char * list_keys[] = {
355    "error",
356    "errorString",
357    "eta",
358    "id",
359    "leftUntilDone",
360    "name",
361    "peersGettingFromUs",
362    "peersSendingToUs",
363    "rateDownload",
364    "rateUpload",
365    "sizeWhenDone",
366    "status",
367    "uploadRatio"
368};
369
370static int
371readargs( int           argc,
372          const char ** argv )
373{
374    int c;
375    int addingTorrents = 0;
376    int status = EXIT_SUCCESS;
377    char id[4096];
378    const char * optarg;
379
380    *id = '\0';
381
382    while( ( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg ) ) )
383    {
384        int     i, n;
385        int     addArg = TRUE;
386        tr_benc top, *args, *fields;
387        tr_bencInitDict( &top, 3 );
388        args = tr_bencDictAddDict( &top, "arguments", 0 );
389
390        switch( c )
391        {
392            case TR_OPT_UNK:
393                if( addingTorrents )
394                {
395                    char * tmp = getEncodedMetainfo( optarg );
396                    if( tmp )
397                    {
398                        tr_bencDictAddStr( &top, "method", "torrent-add" );
399                        tr_bencDictAddStr( args, "metainfo", tmp );
400                        tr_free( tmp );
401                    }
402                    else
403                    {
404                        tr_bencDictAddStr( &top, "method", "torrent-add" );
405                        tr_bencDictAddStr( args, "filename", optarg );
406                    }
407                }
408                else
409                {
410                    fprintf( stderr, "Unknown option: %s\n", optarg );
411                    addArg = FALSE;
412                    status |= EXIT_FAILURE;
413                }
414                break;
415
416            case 'a':
417                addingTorrents = 1;
418                addArg = FALSE;
419                break;
420
421            case 'b':
422                debug = 1;
423                addArg = FALSE;
424                break;
425
426            case 'd':
427                tr_bencDictAddStr( &top, "method", "session-set" );
428                tr_bencDictAddInt( args, TR_PREFS_KEY_DSPEED, numarg( optarg ) );
429                tr_bencDictAddBool( args, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
430                break;
431
432            case 'D':
433                tr_bencDictAddStr( &top, "method", "session-set" );
434                tr_bencDictAddBool( args, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
435                break;
436
437            case 970:
438                tr_bencDictAddStr( &top, "method", "session-set" );
439                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, TRUE );
440                break;
441
442            case 971:
443                tr_bencDictAddStr( &top, "method", "session-set" );
444                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, FALSE );
445                break;
446
447            case 972:
448                tr_bencDictAddStr( &top, "method", "session-set" );
449                tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_DOWN, numarg( optarg ) );
450                break;
451
452            case 973:
453                tr_bencDictAddStr( &top, "method", "session-set" );
454                tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_UP, numarg( optarg ) );
455                break;
456
457            case 974:
458                tr_bencDictAddStr( &top, "method", "session-set" );
459                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, TRUE );
460                break;
461
462            case 975:
463                tr_bencDictAddStr( &top, "method", "session-set" );
464                tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, FALSE );
465                break;
466
467            case 976:
468                tr_bencDictAddStr( &top, "method", "session-set" );
469                addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, optarg);
470                break;
471
472            case 977:
473                tr_bencDictAddStr( &top, "method", "session-set" );
474                addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, optarg);
475                break;
476
477            case 978:
478                tr_bencDictAddStr( &top, "method", "session-set" );
479                addDays( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, optarg );
480                break;
481
482            case 'f':
483                tr_bencDictAddStr( &top, "method", "torrent-get" );
484                tr_bencDictAddInt( &top, "tag", TAG_FILES );
485                addIdArg( args, id );
486                n = TR_N_ELEMENTS( files_keys );
487                fields = tr_bencDictAddList( args, "fields", n );
488                for( i = 0; i < n; ++i )
489                    tr_bencListAddStr( fields, files_keys[i] );
490                break;
491
492            case 'g':
493                tr_bencDictAddStr( &top, "method", "torrent-set" );
494                addIdArg( args, id );
495                addFiles( args, "files-wanted", optarg );
496                break;
497
498            case 'G':
499                tr_bencDictAddStr( &top, "method", "torrent-set" );
500                addIdArg( args, id );
501                addFiles( args, "files-unwanted", optarg );
502                break;
503
504            case 'i':
505                tr_bencDictAddStr( &top, "method", "torrent-get" );
506                tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
507                addIdArg( args, id );
508                n = TR_N_ELEMENTS( details_keys );
509                fields = tr_bencDictAddList( args, "fields", n );
510                for( i = 0; i < n; ++i )
511                    tr_bencListAddStr( fields, details_keys[i] );
512                break;
513
514            case 'l':
515                tr_bencDictAddStr( &top, "method", "torrent-get" );
516                tr_bencDictAddInt( &top, "tag", TAG_LIST );
517                n = TR_N_ELEMENTS( list_keys );
518                fields = tr_bencDictAddList( args, "fields", n );
519                for( i = 0; i < n; ++i )
520                    tr_bencListAddStr( fields, list_keys[i] );
521                break;
522
523            case 'm':
524                tr_bencDictAddStr( &top, "method", "session-set" );
525                tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
526                break;
527
528            case 'M':
529                tr_bencDictAddStr( &top, "method", "session-set" );
530                tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
531                break;
532
533            case 'n':
534                auth = tr_strdup( optarg );
535                addArg = FALSE;
536                break;
537
538            case 'N':
539                netrc = tr_strdup( optarg );
540                addArg = FALSE;
541                break;
542
543            case 'p':
544                tr_bencDictAddStr( &top, "method", "session-set" );
545                tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_PORT, numarg( optarg ) );
546                break;
547
548            case 'P':
549                tr_bencDictAddStr( &top, "method", "session-set" );
550                tr_bencDictAddBool( args, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, TRUE);
551                break;
552
553            case 'r':
554                tr_bencDictAddStr( &top, "method", "torrent-remove" );
555                addIdArg( args, id );
556                break;
557
558            case 'R':
559                tr_bencDictAddStr( &top, "method", "torrent-remove" );
560                addIdArg( args, id );
561                tr_bencDictAddBool( args, "delete-local-data", TRUE );
562                break;
563
564            case 's':
565                tr_bencDictAddStr( &top, "method", "torrent-start" );
566                addIdArg( args, id );
567                break;
568
569            case 'S':
570                tr_bencDictAddStr( &top, "method", "torrent-stop" );
571                addIdArg( args, id );
572                break;
573
574            case 't':
575                tr_strlcpy( id, optarg, sizeof( id ) );
576                addArg = FALSE;
577                break;
578
579            case 'u':
580                tr_bencDictAddStr( &top, "method", "session-set" );
581                tr_bencDictAddInt( args, TR_PREFS_KEY_USPEED, numarg( optarg ) );
582                tr_bencDictAddBool( args, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
583                break;
584
585            case 'U':
586                tr_bencDictAddStr( &top, "method", "session-set" );
587                tr_bencDictAddBool( args, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
588                break;
589
590            case 'v':
591                tr_bencDictAddStr( &top, "method", "torrent-verify" );
592                addIdArg( args, id );
593                break;
594
595            case 'V':
596                fprintf( stderr, "Transmission %s\n", LONG_VERSION_STRING );
597                exit( 0 );
598                break;
599
600            case 'w': {
601                char * path = absolutify( optarg );
602                tr_bencDictAddStr( &top, "method", "session-set" );
603                tr_bencDictAddStr( args, TR_PREFS_KEY_DOWNLOAD_DIR, path );
604                tr_free( path );
605                break;
606            }
607
608            case 'o':
609                tr_bencDictAddStr( &top, "method", "session-set" );
610                tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, TRUE );
611                break;
612
613            case 'O':
614                tr_bencDictAddStr( &top, "method", "session-set" );
615                tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, FALSE );
616                break;
617
618            case 'x':
619                tr_bencDictAddStr( &top, "method", "session-set" );
620                tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, TRUE );
621                break;
622
623            case 'X':
624                tr_bencDictAddStr( &top, "method", "session-set" );
625                tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, FALSE );
626                break;
627
628            case 900:
629                tr_bencDictAddStr( &top, "method", "torrent-set" );
630                addIdArg( args, id );
631                addFiles( args, "priority-high", optarg );
632                break;
633
634            case 901:
635                tr_bencDictAddStr( &top, "method", "torrent-set" );
636                addIdArg( args, id );
637                addFiles( args, "priority-normal", optarg );
638                break;
639
640            case 902:
641                tr_bencDictAddStr( &top, "method", "torrent-set" );
642                addIdArg( args, id );
643                addFiles( args, "priority-low", optarg );
644                break;
645
646            case 910:
647                tr_bencDictAddStr( &top, "method", "session-set" );
648                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "required" );
649                break;
650
651            case 911:
652                tr_bencDictAddStr( &top, "method", "session-set" );
653                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "preferred" );
654                break;
655
656            case 912:
657                tr_bencDictAddStr( &top, "method", "session-set" );
658                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "tolerated" );
659                break;
660
661            case 920:
662                tr_bencDictAddStr( &top, "method", "session-get" );
663                tr_bencDictAddInt( &top, "tag", TAG_SESSION );
664                break;
665
666            case 921:
667                tr_bencDictAddStr( &top, "method", "session-stats" );
668                tr_bencDictAddInt( &top, "tag", TAG_STATS );
669                break;
670               
671            case 930:
672                tr_bencDictAddStr( &top, "method", "torrent-set" );
673                addIdArg( args, id );
674                tr_bencDictAddInt( args, "peer-limit", atoi(optarg) );
675                break;
676
677            case 931:
678                tr_bencDictAddStr( &top, "method", "session-set" );
679                tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi(optarg) );
680                break;
681
682            case 940:
683                tr_bencDictAddStr( &top, "method", "torrent-get" );
684                tr_bencDictAddInt( &top, "tag", TAG_PEERS );
685                addIdArg( args, id );
686                fields = tr_bencDictAddList( args, "fields", 1 );
687                tr_bencListAddStr( fields, "peers" );
688                break;
689
690            case 950:
691                tr_bencDictAddStr( &top, "method", "torrent-set" );
692                tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
693                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_SINGLE );
694                addIdArg( args, id );
695                break;
696
697            case 951:
698                tr_bencDictAddStr( &top, "method", "torrent-set" );
699                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_GLOBAL );
700                addIdArg( args, id );
701                break;
702
703            case 952:
704                tr_bencDictAddStr( &top, "method", "torrent-set" );
705                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_UNLIMITED );
706                addIdArg( args, id );
707                break;
708
709            case 953:
710                tr_bencDictAddStr( &top, "method", "session-set" );
711                tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
712                tr_bencDictAddBool( args, "seedRatioLimited", TRUE );
713                break;
714
715            case 954:
716                tr_bencDictAddStr( &top, "method", "session-set" );
717                tr_bencDictAddBool( args, "seedRatioLimited", FALSE );
718                break;
719
720            case 960:
721                tr_bencDictAddStr( &top, "method", "torrent-set-location" );
722                tr_bencDictAddStr( args, "location", optarg );
723                tr_bencDictAddBool( args, "move", TRUE );
724                addIdArg( args, id );
725                break;
726
727            case 961:
728                tr_bencDictAddStr( &top, "method", "torrent-set-location" );
729                tr_bencDictAddStr( args, "location", optarg );
730                tr_bencDictAddBool( args, "move", FALSE );
731                addIdArg( args, id );
732                break;
733
734            case TR_OPT_ERR:
735                fprintf( stderr, "invalid option\n" );
736                showUsage( );
737                status |= EXIT_FAILURE;
738                break;
739
740            default:
741                fprintf( stderr, "got opt [%d]\n", (int)c );
742                showUsage( );
743                break;
744        }
745
746        if( addArg )
747        {
748            reqs[reqCount++] = tr_bencToStr( &top, TR_FMT_JSON_LEAN, NULL );
749        }
750
751        tr_bencFree( &top );
752    }
753
754    return status;
755}
756
757/* [host:port] or [host] or [port] */
758static void
759getHostAndPort( int * argc, char ** argv, char ** host, int * port )
760{
761    if( *argv[1] != '-' )
762    {
763        int          i;
764        const char * s = argv[1];
765        const char * delim = strchr( s, ':' );
766        if( delim )   /* user passed in both host and port */
767        {
768            *host = tr_strndup( s, delim - s );
769            *port = atoi( delim + 1 );
770        }
771        else
772        {
773            char *    end;
774            const int i = strtol( s, &end, 10 );
775            if( !*end ) /* user passed in a port */
776                *port = i;
777            else /* user passed in a host */
778                *host = tr_strdup( s );
779        }
780
781        *argc -= 1;
782        for( i = 1; i < *argc; ++i )
783            argv[i] = argv[i + 1];
784    }
785}
786
787static size_t
788writeFunc( void * ptr,
789           size_t size,
790           size_t nmemb,
791           void * buf )
792{
793    const size_t byteCount = size * nmemb;
794
795    evbuffer_add( buf, ptr, byteCount );
796    return byteCount;
797}
798
799static char*
800tr_strltime( char * buf, int seconds, size_t buflen )
801{
802    int  days, hours, minutes;
803    char d[128], h[128], m[128], s[128];
804
805    if( seconds < 0 )
806        seconds = 0;
807
808    days = seconds / 86400;
809    hours = ( seconds % 86400 ) / 3600;
810    minutes = ( seconds % 3600 ) / 60;
811    seconds = ( seconds % 3600 ) % 60;
812
813    tr_snprintf( d, sizeof( d ), "%'d day%s", days, days==1?"":"s" );
814    tr_snprintf( h, sizeof( h ), "%'d hour%s", hours, hours==1?"":"s" );
815    tr_snprintf( m, sizeof( m ), "%'d minute%s", minutes, minutes==1?"":"s" );
816    tr_snprintf( s, sizeof( s ), "%'d second%s", seconds, seconds==1?"":"s" );
817
818    if( days )
819    {
820        if( days >= 4 || !hours )
821            tr_strlcpy( buf, d, buflen );
822        else
823            tr_snprintf( buf, buflen, "%s, %s", d, h );
824    }
825    else if( hours )
826    {
827        if( hours >= 4 || !minutes )
828            tr_strlcpy( buf, h, buflen );
829        else
830            tr_snprintf( buf, buflen, "%s, %s", h, m );
831    }
832    else if( minutes )
833    {
834        if( minutes >= 4 || !seconds )
835            tr_strlcpy( buf, m, buflen );
836        else
837            tr_snprintf( buf, buflen, "%s, %s", m, s );
838    }
839    else tr_strlcpy( buf, s, buflen );
840
841    return buf;
842}
843
844#define KILOBYTE_FACTOR 1024.0
845#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
846#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
847
848static char*
849strlratio2( char * buf, double ratio, size_t buflen )
850{
851    return tr_strratio( buf, buflen, ratio, "Inf" );
852}
853
854static char*
855strlratio( char * buf,
856           double numerator,
857           double denominator,
858           size_t buflen )
859{
860    double ratio;
861
862    if( denominator )
863        ratio = numerator / denominator;
864    else if( numerator )
865        ratio = TR_RATIO_INF;
866    else
867        ratio = TR_RATIO_NA;
868
869    return strlratio2( buf, ratio, buflen );
870}
871
872static char*
873strlsize( char *  buf, int64_t size, size_t  buflen )
874{
875    if( !size )
876        tr_strlcpy( buf, "None", buflen );
877    else if( size < (int64_t)KILOBYTE_FACTOR )
878        tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
879    else
880    {
881        double displayed_size;
882        if( size < (int64_t)MEGABYTE_FACTOR )
883        {
884            displayed_size = (double) size / KILOBYTE_FACTOR;
885            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
886        }
887        else if( size < (int64_t)GIGABYTE_FACTOR )
888        {
889            displayed_size = (double) size / MEGABYTE_FACTOR;
890            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
891        }
892        else
893        {
894            displayed_size = (double) size / GIGABYTE_FACTOR;
895            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
896        }
897    }
898    return buf;
899}
900
901static char*
902getStatusString( tr_benc * t, char * buf, size_t buflen )
903{
904    int64_t status;
905
906    if( !tr_bencDictFindInt( t, "status", &status ) )
907    {
908        *buf = '\0';
909    }
910    else switch( status )
911    {
912        case TR_STATUS_STOPPED:
913            tr_strlcpy( buf, "Stopped", buflen );
914            break;
915
916        case TR_STATUS_CHECK_WAIT:
917        case TR_STATUS_CHECK: {
918            const char * str = status == TR_STATUS_CHECK_WAIT
919                             ? "Will Verify"
920                             : "Verifying";
921            double percent;
922            if( tr_bencDictFindReal( t, "recheckProgress", &percent ) )
923                tr_snprintf( buf, buflen, "%s (%.0f%%)", str, floor(percent*100.0) );
924            else
925                tr_strlcpy( buf, str, buflen );
926
927            break;
928        }
929
930        case TR_STATUS_DOWNLOAD:
931        case TR_STATUS_SEED: {
932            int64_t fromUs = 0;
933            int64_t toUs = 0;
934            tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
935            tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
936            if( fromUs && toUs )
937                tr_strlcpy( buf, "Up & Down", buflen );
938            else if( toUs )
939                tr_strlcpy( buf, "Downloading", buflen );
940            else if( fromUs ) {
941                int64_t leftUntilDone = 0;
942                tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
943                if( leftUntilDone > 0 )
944                    tr_strlcpy( buf, "Uploading", buflen );
945                else
946                    tr_strlcpy( buf, "Seeding", buflen );
947            } else {
948                tr_strlcpy( buf, "Idle", buflen );
949            }
950            break;
951        }
952    }
953
954    return buf;
955}
956
957static const char*
958getTrackerDateStr( const time_t t, tr_bool isStopped )
959{
960    const char * str;
961    switch( t ) {
962        case 0: str = isStopped ? "None (Stopped)\n" : "None\n"; break;
963        case 1: str = "In Progress\n"; break;
964        default: str = ctime( &t ); break;
965    }
966    return str;
967}
968
969static void
970printSession( tr_benc * top )
971{
972    tr_benc *args;
973    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
974    {
975        const char * str;
976        int64_t      i;
977        tr_bool      boolVal;
978
979        printf( "VERSION\n" );
980        if( tr_bencDictFindStr( args,  "version", &str ) )
981            printf( "  Daemon version: %s\n", str );
982        if( tr_bencDictFindInt( args, "rpc-version", &i ) )
983            printf( "  RPC version: %" PRId64 "\n", i );
984        if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) )
985            printf( "  RPC minimum version: %" PRId64 "\n", i );
986        printf( "\n" );
987
988        printf( "TRANSFER\n" );
989        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
990            printf( "  Download directory: %s\n", str );
991        if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) )
992            printf( "  Listenport: %" PRId64 "\n", i );
993        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
994            printf( "  Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
995        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
996            printf( "  Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) );
997        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_ENCRYPTION, &str ) )
998            printf( "  Encryption: %s\n", str );
999        printf( "\n" );
1000
1001        {
1002            tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled;
1003            int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit;
1004
1005            if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN, &altDown ) &&
1006                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) &&
1007                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) &&
1008                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) &&
1009                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) &&
1010                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) &&
1011                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP, &altUp ) &&
1012                tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) &&
1013                tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED, &downLimit ) &&
1014                tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) &&
1015                tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED, &upLimit ) &&
1016                tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) )
1017            {
1018                char buf[128];
1019
1020                printf( "LIMITS\n" );
1021                printf( "  Peer limit: %" PRId64 "\n", peerLimit );
1022
1023                if( altEnabled )
1024                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altUp );
1025                else if( upEnabled )
1026                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", upLimit );
1027                else
1028                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1029                printf( "  Upload speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
1030                        buf,
1031                        (upEnabled?"Enabled":"Disabled"), upLimit,
1032                        (altEnabled?"Enabled":"Disabled"), altUp );
1033
1034                if( altEnabled )
1035                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altDown );
1036                else if( downEnabled )
1037                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", downLimit );
1038                else
1039                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1040                printf( "  Download speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
1041                        buf,
1042                        (downEnabled?"Enabled":"Disabled"), downLimit,
1043                        (altEnabled?"Enabled":"Disabled"), altDown );
1044
1045                if( altTimeEnabled ) {
1046                    printf( "  Turtle schedule: %02d:%02d - %02d:%02d  ",
1047                            (int)(altBegin/60), (int)(altBegin%60),
1048                            (int)(altEnd/60), (int)(altEnd%60) );
1049                    if( altDay & TR_SCHED_SUN )   printf( "Sun " );
1050                    if( altDay & TR_SCHED_MON )   printf( "Mon " );
1051                    if( altDay & TR_SCHED_TUES )  printf( "Tue " );
1052                    if( altDay & TR_SCHED_WED )   printf( "Wed " );
1053                    if( altDay & TR_SCHED_THURS ) printf( "Thu " );
1054                    if( altDay & TR_SCHED_FRI )   printf( "Fri " );
1055                    if( altDay & TR_SCHED_SAT )   printf( "Sat " );
1056                    printf( "\n" );
1057                }
1058            }
1059        }
1060    }
1061}
1062
1063static void
1064printSessionStats( tr_benc * top )
1065{
1066    tr_benc *args, *d;
1067    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1068    {
1069        char buf[512];
1070        int64_t up, down, secs, sessions;
1071
1072        if( tr_bencDictFindDict( args, "current-stats", &d )
1073            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1074            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1075            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1076        {
1077            printf( "\nCURRENT SESSION\n" );
1078            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1079            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1080            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1081            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1082        }
1083
1084        if( tr_bencDictFindDict( args, "cumulative-stats", &d )
1085            && tr_bencDictFindInt( d, "sessionCount", &sessions )
1086            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1087            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1088            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1089        {
1090            printf( "\nTOTAL\n" );
1091            printf( "  Started %lu times\n", (unsigned long)sessions );
1092            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1093            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1094            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1095            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1096        }
1097    }
1098}
1099
1100static void
1101printDetails( tr_benc * top )
1102{
1103    tr_benc *args, *torrents;
1104
1105    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1106      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1107    {
1108        int ti, tCount;
1109        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
1110             ++ti )
1111        {
1112            tr_benc *    t = tr_bencListChild( torrents, ti );
1113            tr_benc *    l;
1114            const uint8_t * raw;
1115            size_t       rawlen;
1116            const char * str;
1117            char         buf[512];
1118            char         buf2[512];
1119            int64_t      i, j, k;
1120            tr_bool      isStopped;
1121            tr_bool      boolVal;
1122
1123            isStopped = tr_bencDictFindInt( t, "status", &i ) && (i==TR_STATUS_STOPPED);
1124
1125            printf( "NAME\n" );
1126            if( tr_bencDictFindInt( t, "id", &i ) )
1127                printf( "  Id: %" PRId64 "\n", i );
1128            if( tr_bencDictFindStr( t, "name", &str ) )
1129                printf( "  Name: %s\n", str );
1130            if( tr_bencDictFindStr( t, "hashString", &str ) )
1131                printf( "  Hash: %s\n", str );
1132            printf( "\n" );
1133
1134            printf( "TRANSFER\n" );
1135            getStatusString( t, buf, sizeof( buf ) );
1136            printf( "  State: %s\n", buf );
1137
1138            if( tr_bencDictFindStr( t, "downloadDir", &str ) )
1139                printf( "  Location: %s\n", str );
1140
1141            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1142              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
1143            {
1144                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
1145                printf( "  Percent Done: %s%%\n", buf );
1146            }
1147
1148            if( tr_bencDictFindInt( t, "eta", &i ) )
1149                printf( "  ETA: %s\n", tr_strltime( buf, i, sizeof( buf ) ) );
1150            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
1151                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
1152            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
1153                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
1154            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
1155              && tr_bencDictFindInt( t, "haveValid", &j ) )
1156            {
1157                strlsize( buf, i + j, sizeof( buf ) );
1158                strlsize( buf2, j, sizeof( buf2 ) );
1159                printf( "  Have: %s (%s verified)\n", buf, buf2 );
1160            }
1161
1162            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1163              && tr_bencDictFindInt( t, "totalSize", &j ) )
1164            {
1165                strlsize( buf, j, sizeof( buf ) );
1166                strlsize( buf2, i, sizeof( buf2 ) );
1167                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
1168            }
1169            if( tr_bencDictFindInt( t, "downloadedEver", &i )
1170              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
1171            {
1172                strlsize( buf, i, sizeof( buf ) );
1173                printf( "  Downloaded: %s\n", buf );
1174                strlsize( buf, j, sizeof( buf ) );
1175                printf( "  Uploaded: %s\n", buf );
1176                strlratio( buf, j, i, sizeof( buf ) );
1177                printf( "  Ratio: %s\n", buf );
1178            }
1179            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
1180            {
1181                strlsize( buf, i, sizeof( buf ) );
1182                printf( "  Corrupt DL: %s\n", buf );
1183            }
1184            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str &&
1185                tr_bencDictFindInt( t, "error", &i ) && i )
1186            {
1187                switch( i ) {
1188                    case TR_STAT_TRACKER_WARNING: printf( "  Tracker gave a warning: %s\n", str ); break;
1189                    case TR_STAT_TRACKER_ERROR:   printf( "  Tracker gave an error: %s\n", str ); break;
1190                    case TR_STAT_LOCAL_ERROR:     printf( "  Error: %s\n", str ); break;
1191                    default: break; /* no error */
1192                }
1193            }
1194            if( tr_bencDictFindInt( t, "peersConnected", &i )
1195              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
1196              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
1197            {
1198                printf(
1199                    "  Peers: "
1200                    "connected to %" PRId64 ", "
1201                                            "uploading to %" PRId64
1202                    ", "
1203                    "downloading from %"
1204                    PRId64 "\n",
1205                    i, j, k );
1206            }
1207
1208            if( tr_bencDictFindList( t, "webseeds", &l )
1209              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
1210            {
1211                const int64_t n = tr_bencListSize( l );
1212                if( n > 0 )
1213                    printf(
1214                        "  Web Seeds: downloading from %" PRId64 " of %"
1215                        PRId64
1216                        " web seeds\n", i, n );
1217            }
1218            printf( "\n" );
1219
1220            printf( "HISTORY\n" );
1221            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
1222            {
1223                const time_t tt = i;
1224                printf( "  Date added:      %s", ctime( &tt ) );
1225            }
1226            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
1227            {
1228                const time_t tt = i;
1229                printf( "  Date finished:   %s", ctime( &tt ) );
1230            }
1231            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
1232            {
1233                const time_t tt = i;
1234                printf( "  Date started:    %s", ctime( &tt ) );
1235            }
1236            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
1237            {
1238                const time_t tt = i;
1239                printf( "  Latest activity: %s", ctime( &tt ) );
1240            }
1241            printf( "\n" );
1242
1243            printf( "TRACKER\n" );
1244            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) )
1245                printf( "  Latest announce: %s", getTrackerDateStr( (time_t)i, isStopped ) );
1246            if( tr_bencDictFindStr( t, "announceURL", &str ) )
1247                printf( "  Announce URL: %s\n", str );
1248            if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str )
1249                printf( "  Announce response: %s\n", str );
1250            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) )
1251                printf( "  Next announce:   %s", getTrackerDateStr( (time_t)i, isStopped ) );
1252            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) )
1253                printf( "  Latest scrape:   %s", getTrackerDateStr( (time_t)i, isStopped ) );
1254            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
1255                printf( "  Scrape response: %s\n", str );
1256            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) )
1257                printf( "  Next scrape:     %s", getTrackerDateStr( (time_t)i, isStopped ) );
1258            if( tr_bencDictFindInt( t, "seeders", &i ) && tr_bencDictFindInt( t, "leechers", &j ) )
1259                printf( "  Tracker knows of %" PRId64 " seeders and %" PRId64 " leechers\n", i, j );
1260            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
1261                printf( "  Tracker has seen %" PRId64 " clients complete this torrent\n", i );
1262            printf( "\n" );
1263
1264            printf( "ORIGINS\n" );
1265            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
1266            {
1267                const time_t tt = i;
1268                printf( "  Date created: %s", ctime( &tt ) );
1269            }
1270            if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) )
1271                printf( "  Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) );
1272            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
1273                printf( "  Comment: %s\n", str );
1274            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
1275                printf( "  Creator: %s\n", str );
1276            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
1277                printf( "  Piece Count: %" PRId64 "\n", i );
1278            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
1279                printf( "  Piece Size: %" PRId64 "\n", i );
1280            printf( "\n" );
1281
1282            printf("PIECES\n  ");
1283            if( tr_bencDictFindRaw( t, "pieces", &raw, &rawlen ) && tr_bencDictFindInt( t, "pieceCount", &j ) ) {
1284                int len;
1285                char * str = tr_base64_decode( raw, rawlen, &len );
1286                for( i=k=0; k<len; ++k ) {
1287                    int e;
1288                    for( e=0; i<j && e<8; ++e, ++i )
1289                        printf( str[k] & (1<<(7-e)) ? "1" : "0" );
1290                    printf( " " );
1291                    if( !(i%64) )
1292                        printf( "\n  " );
1293                }
1294                tr_free( str );
1295            }
1296            printf( "\n" );
1297        }
1298    }
1299}
1300
1301static void
1302printFileList( tr_benc * top )
1303{
1304    tr_benc *args, *torrents;
1305
1306    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1307      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1308    {
1309        int i, in;
1310        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
1311        {
1312            tr_benc *    d = tr_bencListChild( torrents, i );
1313            tr_benc *    files, *priorities, *wanteds;
1314            const char * name;
1315            if( tr_bencDictFindStr( d, "name", &name )
1316              && tr_bencDictFindList( d, "files", &files )
1317              && tr_bencDictFindList( d, "priorities", &priorities )
1318              && tr_bencDictFindList( d, "wanted", &wanteds ) )
1319            {
1320                int j = 0, jn = tr_bencListSize( files );
1321                printf( "%s (%d files):\n", name, jn );
1322                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
1323                        "Priority", "Get", "Size",
1324                        "Name" );
1325                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1326                {
1327                    int64_t      have;
1328                    int64_t      length;
1329                    int64_t      priority;
1330                    int64_t      wanted;
1331                    const char * filename;
1332                    tr_benc *    file = tr_bencListChild( files, j );
1333                    if( tr_bencDictFindInt( file, "length", &length )
1334                      && tr_bencDictFindStr( file, "name", &filename )
1335                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
1336                      && tr_bencGetInt( tr_bencListChild( priorities,
1337                                                          j ), &priority )
1338                      && tr_bencGetInt( tr_bencListChild( wanteds,
1339                                                          j ), &wanted ) )
1340                    {
1341                        char         sizestr[64];
1342                        double       percent = (double)have / length;
1343                        const char * pristr;
1344                        strlsize( sizestr, length, sizeof( sizestr ) );
1345                        switch( priority )
1346                        {
1347                            case TR_PRI_LOW:
1348                                pristr = "Low"; break;
1349
1350                            case TR_PRI_HIGH:
1351                                pristr = "High"; break;
1352
1353                            default:
1354                                pristr = "Normal"; break;
1355                        }
1356                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
1357                                j,
1358                                floor( 100.0 * percent ),
1359                                pristr,
1360                                ( wanted ? "Yes" : "No" ),
1361                                sizestr,
1362                                filename );
1363                    }
1364                }
1365            }
1366        }
1367    }
1368}
1369
1370static void
1371printPeersImpl( tr_benc * peers )
1372{
1373    int i, n;
1374    printf( "%-20s  %-12s  %-5s %-6s  %-6s  %s\n",
1375            "Address", "Flags", "Done", "Down", "Up", "Client" );
1376    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1377    {
1378        double progress;
1379        const char * address, * client, * flagstr;
1380        int64_t rateToClient, rateToPeer;
1381        tr_benc * d = tr_bencListChild( peers, i );
1382
1383        if( tr_bencDictFindStr( d, "address", &address )
1384          && tr_bencDictFindStr( d, "clientName", &client )
1385          && tr_bencDictFindReal( d, "progress", &progress )
1386          && tr_bencDictFindStr( d, "flagStr", &flagstr )
1387          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1388          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1389        {
1390            printf( "%-20s  %-12s  %-5.1f %6.1f  %6.1f  %s\n",
1391                    address, flagstr, (progress*100.0),
1392                    rateToClient / 1024.0,
1393                    rateToPeer / 1024.0,
1394                    client );
1395        }
1396    }
1397}
1398
1399static void
1400printPeers( 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, n;
1408        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1409        {
1410            tr_benc * peers;
1411            tr_benc * torrent = tr_bencListChild( torrents, i );
1412            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1413                printPeersImpl( peers );
1414                if( i+1<n )
1415                    printf( "\n" );
1416            }
1417        }
1418    }
1419}
1420
1421static void
1422printTorrentList( tr_benc * top )
1423{
1424    tr_benc *args, *list;
1425
1426    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1427      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1428    {
1429        int i, n;
1430        int64_t total_up = 0, total_down = 0, total_size = 0;
1431        char haveStr[32];
1432
1433        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1434                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1435                "Name" );
1436
1437        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1438        {
1439            int64_t      id, eta, status, up, down;
1440            int64_t      sizeWhenDone, leftUntilDone;
1441            double       ratio;
1442            const char * name;
1443            tr_benc *   d = tr_bencListChild( list, i );
1444            if( tr_bencDictFindInt( d, "eta", &eta )
1445              && tr_bencDictFindInt( d, "id", &id )
1446              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1447              && tr_bencDictFindStr( d, "name", &name )
1448              && tr_bencDictFindInt( d, "rateDownload", &down )
1449              && tr_bencDictFindInt( d, "rateUpload", &up )
1450              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1451              && tr_bencDictFindInt( d, "status", &status )
1452              && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1453            {
1454                char etaStr[16];
1455                char statusStr[64];
1456                char ratioStr[32];
1457                char doneStr[8];
1458                int64_t error;
1459                char errorMark;
1460
1461                if( sizeWhenDone )
1462                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1463                else
1464                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1465
1466                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1467
1468                if( leftUntilDone )
1469                    tr_strltime( etaStr, eta, sizeof( etaStr ) );
1470                else
1471                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1472                if( tr_bencDictFindInt( d, "error", &error ) && error )
1473                    errorMark = '*';
1474                else
1475                    errorMark = ' ';
1476                printf(
1477                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1478                    (int)id, errorMark,
1479                    doneStr,
1480                    haveStr,
1481                    etaStr,
1482                    up / 1024.0,
1483                    down / 1024.0,
1484                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1485                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1486                    name );
1487
1488                total_up += up;
1489                total_down += down;
1490                total_size += sizeWhenDone - leftUntilDone;
1491            }
1492        }
1493
1494        printf( "Sum:         %9s            %6.1f  %6.1f\n",
1495                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1496                total_up / 1024.0,
1497                total_down / 1024.0 );
1498    }
1499}
1500
1501static int
1502processResponse( const char * host,
1503                 int          port,
1504                 const void * response,
1505                 size_t       len )
1506{
1507    tr_benc top;
1508    int status = EXIT_SUCCESS;
1509
1510    if( debug )
1511        fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1512                 (int)len, (int)len, (int)len, (const char*) response );
1513
1514    if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1515    {
1516        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1517                 (int)len, (char*)response );
1518        status |= EXIT_FAILURE;
1519    }
1520    else
1521    {
1522        int64_t      tag = -1;
1523        const char * str;
1524        tr_bencDictFindInt( &top, "tag", &tag );
1525
1526        switch( tag )
1527        {
1528            case TAG_SESSION:
1529                printSession( &top ); break;
1530
1531            case TAG_STATS:
1532                printSessionStats( &top ); break;
1533
1534            case TAG_FILES:
1535                printFileList( &top ); break;
1536
1537            case TAG_DETAILS:
1538                printDetails( &top ); break;
1539
1540            case TAG_LIST:
1541                printTorrentList( &top ); break;
1542
1543            case TAG_PEERS:
1544                printPeers( &top ); break;
1545
1546            default:
1547                if( !tr_bencDictFindStr( &top, "result", &str ) )
1548                    status |= EXIT_FAILURE;
1549                else {
1550                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1551                    if( strcmp( str, "success") )
1552                        status |= EXIT_FAILURE;
1553                }
1554        }
1555
1556        tr_bencFree( &top );
1557    }
1558
1559    return status;
1560}
1561
1562/* look for a session id in the header in case the server gives back a 409 */
1563static size_t
1564parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
1565{
1566    const char * line = ptr;
1567    const size_t line_len = size * nmemb;
1568    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
1569    const size_t key_len = strlen( key );
1570
1571    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
1572    {
1573        const char * begin = line + key_len;
1574        const char * end = begin;
1575        while( !isspace( *end ) )
1576            ++end;
1577        tr_free( sessionId );
1578        sessionId = tr_strndup( begin, end-begin );
1579    }
1580
1581    return line_len;
1582}
1583
1584static CURL*
1585tr_curl_easy_init( struct evbuffer * writebuf )
1586{
1587    CURL * curl = curl_easy_init( );
1588    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1589    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1590    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1591    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1592    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1593    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1594    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1595    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
1596    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1597    curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1598    if( netrc )
1599        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1600    if( auth )
1601        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1602    if( sessionId ) {
1603        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1604        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1605        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1606        /* fixme: leaks */
1607    }
1608    return curl;
1609}
1610
1611
1612static int
1613processRequests( const char *  host,
1614                 int           port,
1615                 const char ** reqs,
1616                 int           reqCount )
1617{
1618    int i;
1619    CURL * curl = NULL;
1620    struct evbuffer * buf = evbuffer_new( );
1621    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1622    int status = EXIT_SUCCESS;
1623
1624    for( i=0; i<reqCount; ++i )
1625    {
1626        CURLcode res;
1627        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1628
1629        if( curl == NULL )
1630        {
1631            curl = tr_curl_easy_init( buf );
1632            curl_easy_setopt( curl, CURLOPT_URL, url );
1633        }
1634
1635        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1636
1637        if( debug )
1638            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
1639        if( ( res = curl_easy_perform( curl ) ) )
1640        {
1641            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
1642            status |= EXIT_FAILURE;
1643        }
1644        else {
1645            long response;
1646            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1647            switch( response ) {
1648                case 200:
1649                    status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1650                    break;
1651                case 409:
1652                    /* session id failed.  our curl header func has already
1653                     * pulled the new session id from this response's headers,
1654                     * build a new CURL* and try again */
1655                    curl_easy_cleanup( curl );
1656                    curl = NULL;
1657                    --i;
1658                    break;
1659                default:
1660                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1661                    status |= EXIT_FAILURE;
1662                    break;
1663            }
1664        }
1665    }
1666
1667    /* cleanup */
1668    tr_free( url );
1669    evbuffer_free( buf );
1670    if( curl != NULL )
1671        curl_easy_cleanup( curl );
1672    return status;
1673}
1674
1675int
1676main( int     argc,
1677      char ** argv )
1678{
1679    int    i;
1680    int    port = DEFAULT_PORT;
1681    char * host = NULL;
1682    int    exit_status = EXIT_SUCCESS;
1683
1684    if( argc < 2 ) {
1685        showUsage( );
1686        return EXIT_FAILURE;
1687    }
1688
1689    getHostAndPort( &argc, argv, &host, &port );
1690    if( host == NULL )
1691        host = tr_strdup( DEFAULT_HOST );
1692
1693    exit_status |= readargs( argc, (const char**)argv );
1694    if( reqCount )
1695        exit_status = processRequests( host, port, (const char**)reqs, reqCount );
1696    else {
1697        showUsage( );
1698        return EXIT_FAILURE;
1699    }
1700
1701
1702    for( i=0; i<reqCount; ++i )
1703        tr_free( reqs[i] );
1704
1705    tr_free( host );
1706    return exit_status;
1707}
1708
Note: See TracBrowser for help on using the repository browser.