source: trunk/daemon/remote.c @ 11598

Last change on this file since 11598 was 11598, checked in by charles, 11 years ago

(trunk daemon) tweak the "transmission-remote --info" display for downloading/seeding time

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