source: trunk/daemon/remote.c @ 13266

Last change on this file since 13266 was 13266, checked in by jordan, 9 years ago

add #4846 'Show magnet links in torrent info in transmission-remote -i' from a patch by eSyr

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