source: trunk/daemon/remote.c @ 11209

Last change on this file since 11209 was 11209, checked in by Longinus00, 12 years ago

switch trackerRemove and trackerReplace rpc calls to use tracker id instead of announce urls as identifiers

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