source: trunk/daemon/remote.c @ 11864

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

(trunk libt) silence a couple of identical compiler warnings: "ignoring return value of ‘getcwd’, declared with attribute warn_unused_result".

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