source: trunk/daemon/remote.c @ 11809

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

(trunk daemon) #3775 "'-tall' no longer works for transmission-remote" -- fixed.

looks like this was broken in r10907 for #3368 in 2.00 when we added "-ta" as the short form of "--tracker-add". "-ta" seems to be conflicting with "-tall". This can be fixed by changing --tracker-add's short form.

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