source: trunk/daemon/remote.c @ 11865

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

(trunk) improvement to r11864

Since the contents of getcwd() are undefined on error, explicitly terminate the buffer string if getcwd() fails.

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