source: trunk/daemon/remote.c @ 10955

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

(trunk T) #3045 "units" -- modify the formatter functions based on feedback from BMW

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