source: trunk/daemon/remote.c @ 12204

Last change on this file since 12204 was 12204, checked in by jordan, 11 years ago

(trunk) #4138 "use stdbool.h instead of tr_bool" -- done.

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