source: trunk/daemon/remote.c @ 11163

Last change on this file since 11163 was 11163, checked in by charles, 12 years ago

(trunk) make --version/-V behave the same in the other apps too

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