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