source: trunk/daemon/remote.c @ 10967

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

(trunk daemon) #3400 "transmission-remote does not show running torrents" -- fixed.

  • Property svn:keywords set to Date Rev Author Id
File size: 84.4 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 10967 2010-07-07 17:53:16Z 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_bencDictFindReal( t, "rateDownload", &d ) )
825                printf( "  Download Speed: %s\n", tr_formatter_speed_KBps( buf, d, sizeof( buf ) ) );
826            if( tr_bencDictFindReal( t, "rateUpload", &d ) )
827                printf( "  Upload Speed: %s\n", tr_formatter_speed_KBps( buf, d, 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_size=0;
1286        double total_up=0, total_down=0;
1287        char haveStr[32];
1288
1289        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1290                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1291                "Name" );
1292
1293        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1294        {
1295            int64_t      id, eta, status;
1296            int64_t      sizeWhenDone, leftUntilDone;
1297            double       ratio, up, down;
1298            const char * name;
1299            tr_benc *   d = tr_bencListChild( list, i );
1300            if( tr_bencDictFindInt( d, "eta", &eta )
1301              && tr_bencDictFindInt( d, "id", &id )
1302              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1303              && tr_bencDictFindStr( d, "name", &name )
1304              && tr_bencDictFindReal( d, "rateDownload", &down )
1305              && tr_bencDictFindReal( d, "rateUpload", &up )
1306              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1307              && tr_bencDictFindInt( d, "status", &status )
1308              && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1309            {
1310                char etaStr[16];
1311                char statusStr[64];
1312                char ratioStr[32];
1313                char doneStr[8];
1314                int64_t error;
1315                char errorMark;
1316
1317                if( sizeWhenDone )
1318                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1319                else
1320                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1321
1322                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1323
1324                if( leftUntilDone || eta != -1 )
1325                    etaToString( etaStr, sizeof( etaStr ), eta );
1326                else
1327                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1328                if( tr_bencDictFindInt( d, "error", &error ) && error )
1329                    errorMark = '*';
1330                else
1331                    errorMark = ' ';
1332                printf(
1333                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1334                    (int)id, errorMark,
1335                    doneStr,
1336                    haveStr,
1337                    etaStr,
1338                    up,
1339                    down,
1340                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1341                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1342                    name );
1343
1344                total_up += up;
1345                total_down += down;
1346                total_size += sizeWhenDone - leftUntilDone;
1347            }
1348        }
1349
1350        printf( "Sum:         %9s            %6.1f  %6.1f\n",
1351                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1352                total_up,
1353                total_down );
1354    }
1355}
1356
1357static void
1358printSession( tr_benc * top )
1359{
1360    tr_benc *args;
1361    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1362    {
1363        int64_t i;
1364        char buf[64];
1365        tr_bool boolVal;
1366        const char * str;
1367
1368        printf( "VERSION\n" );
1369        if( tr_bencDictFindStr( args,  "version", &str ) )
1370            printf( "  Daemon version: %s\n", str );
1371        if( tr_bencDictFindInt( args, "rpc-version", &i ) )
1372            printf( "  RPC version: %" PRId64 "\n", i );
1373        if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) )
1374            printf( "  RPC minimum version: %" PRId64 "\n", i );
1375        printf( "\n" );
1376
1377        printf( "CONFIG\n" );
1378        if( tr_bencDictFindStr( args, "config-dir", &str ) )
1379            printf( "  Configuration directory: %s\n", str );
1380        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
1381            printf( "  Download directory: %s\n", str );
1382        if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) )
1383            printf( "  Listenport: %" PRId64 "\n", i );
1384        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
1385            printf( "  Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1386        if( tr_bencDictFindBool( args, TR_PREFS_KEY_DHT_ENABLED, &boolVal ) )
1387            printf( "  Distributed hash table enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1388        if( tr_bencDictFindBool( args, TR_PREFS_KEY_LPD_ENABLED, &boolVal ) )
1389            printf( "  Local peer discovery enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1390        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
1391            printf( "  Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) );
1392        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_ENCRYPTION, &str ) )
1393            printf( "  Encryption: %s\n", str );
1394        if( tr_bencDictFindInt( args, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, &i ) )
1395            printf( "  Maximum memory cache size: %s\n", tr_formatter_mem_MB( buf, i, sizeof( buf ) ) );
1396        printf( "\n" );
1397
1398        {
1399            tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled, seedRatioLimited;
1400            int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit;
1401            double seedRatioLimit;
1402
1403            if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, &altDown ) &&
1404                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) &&
1405                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) &&
1406                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) &&
1407                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) &&
1408                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) &&
1409                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP_KBps, &altUp ) &&
1410                tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) &&
1411                tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED_KBps, &downLimit ) &&
1412                tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) &&
1413                tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED_KBps, &upLimit ) &&
1414                tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) &&
1415                tr_bencDictFindReal( args, "seedRatioLimit", &seedRatioLimit ) &&
1416                tr_bencDictFindBool( args, "seedRatioLimited", &seedRatioLimited) )
1417            {
1418                char buf[128];
1419                char buf2[128];
1420                char buf3[128];
1421
1422                printf( "LIMITS\n" );
1423                printf( "  Peer limit: %" PRId64 "\n", peerLimit );
1424
1425                if( seedRatioLimited )
1426                    tr_snprintf( buf, sizeof( buf ), "%.2f", seedRatioLimit );
1427                else
1428                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1429                printf( "  Default seed ratio limit: %s\n", buf );
1430
1431                if( altEnabled )
1432                    tr_formatter_speed_KBps( buf, altUp, sizeof( buf ) );
1433                else if( upEnabled )
1434                    tr_formatter_speed_KBps( buf, upLimit, sizeof( buf ) );
1435                else
1436                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1437                printf( "  Upload speed limit: %s  (%s limit: %s; %s turtle limit: %s)\n",
1438                        buf,
1439                        upEnabled ? "Enabled" : "Disabled",
1440                        tr_formatter_speed_KBps( buf2, upLimit, sizeof( buf2 ) ),
1441                        altEnabled ? "Enabled" : "Disabled",
1442                        tr_formatter_speed_KBps( buf3, altUp, sizeof( buf3 ) ) );
1443
1444                if( altEnabled )
1445                    tr_formatter_speed_KBps( buf, altDown, sizeof( buf ) );
1446                else if( downEnabled )
1447                    tr_formatter_speed_KBps( buf, downLimit, sizeof( buf ) );
1448                else
1449                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1450                printf( "  Download speed limit: %s  (%s limit: %s; %s turtle limit: %s)\n",
1451                        buf,
1452                        downEnabled ? "Enabled" : "Disabled",
1453                        tr_formatter_speed_KBps( buf2, downLimit, sizeof( buf2 ) ),
1454                        altEnabled ? "Enabled" : "Disabled",
1455                        tr_formatter_speed_KBps( buf2, altDown, sizeof( buf2 ) ) );
1456
1457                if( altTimeEnabled ) {
1458                    printf( "  Turtle schedule: %02d:%02d - %02d:%02d  ",
1459                            (int)(altBegin/60), (int)(altBegin%60),
1460                            (int)(altEnd/60), (int)(altEnd%60) );
1461                    if( altDay & TR_SCHED_SUN )   printf( "Sun " );
1462                    if( altDay & TR_SCHED_MON )   printf( "Mon " );
1463                    if( altDay & TR_SCHED_TUES )  printf( "Tue " );
1464                    if( altDay & TR_SCHED_WED )   printf( "Wed " );
1465                    if( altDay & TR_SCHED_THURS ) printf( "Thu " );
1466                    if( altDay & TR_SCHED_FRI )   printf( "Fri " );
1467                    if( altDay & TR_SCHED_SAT )   printf( "Sat " );
1468                    printf( "\n" );
1469                }
1470            }
1471        }
1472        printf( "\n" );
1473
1474        printf( "MISC\n" );
1475        if( tr_bencDictFindBool( args, TR_PREFS_KEY_START, &boolVal ) )
1476            printf( "  Autostart added torrents: %s\n", ( boolVal ? "Yes" : "No" ) );
1477        if( tr_bencDictFindBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, &boolVal ) )
1478            printf( "  Delete automatically added torrents: %s\n", ( boolVal ? "Yes" : "No" ) );
1479    }
1480}
1481
1482static void
1483printSessionStats( tr_benc * top )
1484{
1485    tr_benc *args, *d;
1486    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1487    {
1488        char buf[512];
1489        int64_t up, down, secs, sessions;
1490
1491        if( tr_bencDictFindDict( args, "current-stats", &d )
1492            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1493            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1494            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1495        {
1496            printf( "\nCURRENT SESSION\n" );
1497            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1498            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1499            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1500            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1501        }
1502
1503        if( tr_bencDictFindDict( args, "cumulative-stats", &d )
1504            && tr_bencDictFindInt( d, "sessionCount", &sessions )
1505            && tr_bencDictFindInt( d, "uploadedBytes", &up )
1506            && tr_bencDictFindInt( d, "downloadedBytes", &down )
1507            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1508        {
1509            printf( "\nTOTAL\n" );
1510            printf( "  Started %lu times\n", (unsigned long)sessions );
1511            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
1512            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1513            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1514            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1515        }
1516    }
1517}
1518
1519static char id[4096];
1520
1521static int
1522processResponse( const char * host, int port, const void * response, size_t len )
1523{
1524    tr_benc top;
1525    int status = EXIT_SUCCESS;
1526
1527    if( debug )
1528        fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1529                 (int)len, (int)len, (int)len, (const char*) response );
1530
1531    if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1532    {
1533        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1534                 (int)len, (char*)response );
1535        status |= EXIT_FAILURE;
1536    }
1537    else
1538    {
1539        int64_t      tag = -1;
1540        const char * str;
1541        tr_bencDictFindInt( &top, "tag", &tag );
1542
1543        switch( tag )
1544        {
1545            case TAG_SESSION:
1546                printSession( &top ); break;
1547
1548            case TAG_STATS:
1549                printSessionStats( &top ); break;
1550
1551            case TAG_FILES:
1552                printFileList( &top ); break;
1553
1554            case TAG_DETAILS:
1555                printDetails( &top ); break;
1556
1557            case TAG_LIST:
1558                printTorrentList( &top ); break;
1559
1560            case TAG_PEERS:
1561                printPeers( &top ); break;
1562
1563            case TAG_PORTTEST:
1564                printPortTest( &top ); break;
1565
1566            case TAG_TORRENT_ADD: {
1567                int64_t i;
1568                tr_benc * b = &top;
1569                if( tr_bencDictFindDict( &top, ARGUMENTS, &b )
1570                        && tr_bencDictFindDict( b, "torrent-added", &b )
1571                        && tr_bencDictFindInt( b, "id", &i ) )
1572                    tr_snprintf( id, sizeof(id), "%"PRId64, i );
1573                /* fall-through to default: to give success or failure msg */
1574            }
1575
1576            default:
1577                if( !tr_bencDictFindStr( &top, "result", &str ) )
1578                    status |= EXIT_FAILURE;
1579                else {
1580                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1581                    if( strcmp( str, "success") )
1582                        status |= EXIT_FAILURE;
1583                }
1584        }
1585
1586        tr_bencFree( &top );
1587    }
1588
1589    return status;
1590}
1591
1592static CURL*
1593tr_curl_easy_init( struct evbuffer * writebuf )
1594{
1595    CURL * curl = curl_easy_init( );
1596    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1597    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1598    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1599    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1600    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1601    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1602    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1603    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1604    curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1605    if( netrc )
1606        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1607    if( auth )
1608        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1609    if( sessionId ) {
1610        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1611        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1612        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1613        /* fixme: leaks */
1614    }
1615    return curl;
1616}
1617
1618static int
1619flush( const char * host, int port, tr_benc ** benc )
1620{
1621    CURLcode res;
1622    CURL * curl;
1623    int status = EXIT_SUCCESS;
1624    struct evbuffer * buf = evbuffer_new( );
1625    char * json = tr_bencToStr( *benc, TR_FMT_JSON_LEAN, NULL );
1626    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1627
1628    curl = tr_curl_easy_init( buf );
1629    curl_easy_setopt( curl, CURLOPT_URL, url );
1630    curl_easy_setopt( curl, CURLOPT_POSTFIELDS, json );
1631    curl_easy_setopt( curl, CURLOPT_TIMEOUT, getTimeoutSecs( json ) );
1632
1633    if( debug )
1634        fprintf( stderr, "posting:\n--------\n%s\n--------\n", json );
1635
1636    if(( res = curl_easy_perform( curl )))
1637    {
1638        tr_nerr( MY_NAME, "(%s) %s", url, curl_easy_strerror( res ) );
1639        status |= EXIT_FAILURE;
1640    }
1641    else
1642    {
1643        long response;
1644        curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1645        switch( response ) {
1646            case 200:
1647                status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1648                break;
1649            case 409:
1650                /* session id failed.  our curl header func has already
1651                * pulled the new session id from this response's headers,
1652                * build a new CURL* and try again */
1653                curl_easy_cleanup( curl );
1654                curl = NULL;
1655                flush( host, port, benc );
1656                benc = NULL;
1657                break;
1658            default:
1659                fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1660                status |= EXIT_FAILURE;
1661                break;
1662        }
1663    }
1664
1665    /* cleanup */
1666    tr_free( url );
1667    tr_free( json );
1668    evbuffer_free( buf );
1669    if( curl != 0 )
1670        curl_easy_cleanup( curl );
1671    if( benc != NULL ) {
1672        tr_bencFree( *benc );
1673        *benc = 0;
1674    }
1675    return status;
1676}
1677
1678static tr_benc*
1679ensure_sset( tr_benc ** sset )
1680{
1681    tr_benc * args;
1682
1683    if( *sset )
1684        args = tr_bencDictFind( *sset, ARGUMENTS );
1685    else {
1686        *sset = tr_new0( tr_benc, 1 );
1687        tr_bencInitDict( *sset, 3 );
1688        tr_bencDictAddStr( *sset, "method", "session-set" );
1689        args = tr_bencDictAddDict( *sset, ARGUMENTS, 0 );
1690    }
1691
1692    return args;
1693}
1694
1695static tr_benc*
1696ensure_tset( tr_benc ** tset )
1697{
1698    tr_benc * args;
1699
1700    if( *tset )
1701        args = tr_bencDictFind( *tset, ARGUMENTS );
1702    else {
1703        *tset = tr_new0( tr_benc, 1 );
1704        tr_bencInitDict( *tset, 3 );
1705        tr_bencDictAddStr( *tset, "method", "torrent-set" );
1706        args = tr_bencDictAddDict( *tset, ARGUMENTS, 1 );
1707    }
1708
1709    return args;
1710}
1711
1712static int
1713processArgs( const char * host, int port, int argc, const char ** argv )
1714{
1715    int c;
1716    int status = EXIT_SUCCESS;
1717    const char * optarg;
1718    tr_benc *sset = 0;
1719    tr_benc *tset = 0;
1720    tr_benc *tadd = 0;
1721
1722    *id = '\0';
1723
1724    while(( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg )))
1725    {
1726        const int stepMode = getOptMode( c );
1727
1728        if( !stepMode ) /* meta commands */
1729        {
1730            switch( c )
1731            {
1732                case 'a': /* add torrent */
1733                    if( tadd != 0 ) status |= flush( host, port, &tadd );
1734                    if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
1735                    tadd = tr_new0( tr_benc, 1 );
1736                    tr_bencInitDict( tadd, 3 );
1737                    tr_bencDictAddStr( tadd, "method", "torrent-add" );
1738                    tr_bencDictAddInt( tadd, "tag", TAG_TORRENT_ADD );
1739                    tr_bencDictAddDict( tadd, ARGUMENTS, 0 );
1740                    break;
1741
1742                case 'b': /* debug */
1743                    debug = TRUE;
1744                    break;
1745
1746                case 'n': /* auth */
1747                    auth = tr_strdup( optarg );
1748                    break;
1749
1750                case 'N': /* netrc */
1751                    netrc = tr_strdup( optarg );
1752                    break;
1753
1754                case 't': /* set current torrent */
1755                    if( tadd != 0 ) status |= flush( host, port, &tadd );
1756                    if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
1757                    tr_strlcpy( id, optarg, sizeof( id ) );
1758                    break;
1759
1760                case 'V': /* show version number */
1761                    fprintf( stderr, "Transmission %s\n", LONG_VERSION_STRING );
1762                    exit( 0 );
1763                    break;
1764
1765                case TR_OPT_ERR:
1766                    fprintf( stderr, "invalid option\n" );
1767                    showUsage( );
1768                    status |= EXIT_FAILURE;
1769                    break;
1770
1771                case TR_OPT_UNK:
1772                    if( tadd ) {
1773                        tr_benc * args = tr_bencDictFind( tadd, ARGUMENTS );
1774                        char * tmp = getEncodedMetainfo( optarg );
1775                        if( tmp )
1776                            tr_bencDictAddStr( args, "metainfo", tmp );
1777                        else
1778                            tr_bencDictAddStr( args, "filename", optarg );
1779                        tr_free( tmp );
1780                    } else {
1781                        fprintf( stderr, "Unknown option: %s\n", optarg );
1782                        status |= EXIT_FAILURE;
1783                    }
1784                    break;
1785            }
1786        }
1787        else if( stepMode == MODE_TORRENT_GET )
1788        {
1789            size_t i, n;
1790            tr_benc * top = tr_new0( tr_benc, 1 );
1791            tr_benc * args;
1792            tr_benc * fields;
1793            tr_bencInitDict( top, 3 );
1794            tr_bencDictAddStr( top, "method", "torrent-get" );
1795            args = tr_bencDictAddDict( top, ARGUMENTS, 0 );
1796            fields = tr_bencDictAddList( args, "fields", 0 );
1797
1798            if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
1799           
1800            switch( c )
1801            {
1802                case 'f': tr_bencDictAddInt( top, "tag", TAG_FILES );
1803                          n = TR_N_ELEMENTS( files_keys );
1804                          for( i=0; i<n; ++i ) tr_bencListAddStr( fields, files_keys[i] );
1805                          addIdArg( args, id );
1806                          break;
1807                case 'i': tr_bencDictAddInt( top, "tag", TAG_DETAILS );
1808                          n = TR_N_ELEMENTS( details_keys );
1809                          for( i=0; i<n; ++i ) tr_bencListAddStr( fields, details_keys[i] );
1810                          addIdArg( args, id );
1811                          break;
1812                case 'l': tr_bencDictAddInt( top, "tag", TAG_LIST );
1813                          n = TR_N_ELEMENTS( list_keys );
1814                          for( i=0; i<n; ++i ) tr_bencListAddStr( fields, list_keys[i] );
1815                          break;
1816                case 940: tr_bencDictAddInt( top, "tag", TAG_PEERS );
1817                          tr_bencListAddStr( fields, "peers" );
1818                          addIdArg( args, id );
1819                          break;
1820                default:  assert( "unhandled value" && 0 );
1821            }
1822
1823            status |= flush( host, port, &top );
1824        }
1825        else if( stepMode == MODE_SESSION_SET )
1826        {
1827            tr_benc * args = ensure_sset( &sset );
1828
1829            switch( c )
1830            {
1831                case 800: tr_bencDictAddStr( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, optarg );
1832                          tr_bencDictAddBool( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, TRUE );
1833                          break;
1834                case 801: tr_bencDictAddBool( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, FALSE );
1835                          break;
1836                case 970: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, TRUE );
1837                          break;
1838                case 971: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, FALSE );
1839                          break;
1840                case 972: tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, numarg( optarg ) );
1841                          break;
1842                case 973: tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_UP_KBps, numarg( optarg ) );
1843                          break;
1844                case 974: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, TRUE );
1845                          break;
1846                case 975: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, FALSE );
1847                          break;
1848                case 976: addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, optarg );
1849                          break;
1850                case 977: addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, optarg );
1851                          break;
1852                case 978: addDays( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, optarg );
1853                          break;
1854                case 'c': tr_bencDictAddStr( args, TR_PREFS_KEY_INCOMPLETE_DIR, optarg );
1855                          tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, TRUE );
1856                          break;
1857                case 'C': tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, FALSE );
1858                          break;
1859                case 'e': tr_bencDictAddReal( args, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, atof(optarg) );
1860                          break;
1861                case 910: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "required" );
1862                          break;
1863                case 911: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "preferred" );
1864                          break;
1865                case 912: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "tolerated" );
1866                          break;
1867                case 'm': tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
1868                          break;
1869                case 'M': tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
1870                          break;
1871                case 'o': tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, TRUE );
1872                          break;
1873                case 'O': tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, FALSE );
1874                          break;
1875                case 'p': tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_PORT, numarg( optarg ) );
1876                          break;
1877                case 'P': tr_bencDictAddBool( args, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, TRUE);
1878                          break;
1879                case 'x': tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, TRUE );
1880                          break;
1881                case 'X': tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, FALSE );
1882                          break;
1883                case 'y': tr_bencDictAddBool( args, TR_PREFS_KEY_LPD_ENABLED, TRUE );
1884                          break;
1885                case 'Y': tr_bencDictAddBool( args, TR_PREFS_KEY_LPD_ENABLED, FALSE );
1886                          break;
1887                case 953: tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
1888                          tr_bencDictAddBool( args, "seedRatioLimited", TRUE );
1889                          break;
1890                case 954: tr_bencDictAddBool( args, "seedRatioLimited", FALSE );
1891                          break;
1892                case 990: tr_bencDictAddBool( args, TR_PREFS_KEY_START, FALSE );
1893                          break;
1894                case 991: tr_bencDictAddBool( args, TR_PREFS_KEY_START, TRUE );
1895                          break;
1896                case 992: tr_bencDictAddBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, TRUE );
1897                          break;
1898                case 993: tr_bencDictAddBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, FALSE );
1899                          break;
1900                default:  assert( "unhandled value" && 0 );
1901                          break;
1902            }
1903        }
1904        else if( stepMode == ( MODE_SESSION_SET | MODE_TORRENT_SET ) )
1905        {
1906            tr_benc * targs = 0;
1907            tr_benc * sargs = 0;
1908
1909            if( *id )
1910                targs = ensure_tset( &tset );
1911            else
1912                sargs = ensure_sset( &sset );
1913           
1914            switch( c )
1915            {
1916                case 'd': if( targs ) {
1917                              tr_bencDictAddInt( targs, "downloadLimit", numarg( optarg ) );
1918                              tr_bencDictAddBool( targs, "downloadLimited", TRUE );
1919                          } else {
1920                              tr_bencDictAddInt( sargs, TR_PREFS_KEY_DSPEED_KBps, numarg( optarg ) );
1921                              tr_bencDictAddBool( sargs, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
1922                          }
1923                          break;
1924                case 'D': if( targs )
1925                              tr_bencDictAddBool( targs, "downloadLimited", FALSE );
1926                          else
1927                              tr_bencDictAddBool( sargs, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
1928                          break;
1929                case 'u': if( targs ) {
1930                              tr_bencDictAddInt( targs, "uploadLimit", numarg( optarg ) );
1931                              tr_bencDictAddBool( targs, "uploadLimited", TRUE );
1932                          } else {
1933                              tr_bencDictAddInt( sargs, TR_PREFS_KEY_USPEED_KBps, numarg( optarg ) );
1934                              tr_bencDictAddBool( sargs, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
1935                          }
1936                          break;
1937                case 'U': if( targs )
1938                              tr_bencDictAddBool( targs, "uploadLimited", FALSE );
1939                          else
1940                              tr_bencDictAddBool( sargs, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
1941                          break;
1942                case 930: if( targs )
1943                              tr_bencDictAddInt( targs, "peer-limit", atoi(optarg) );
1944                          else
1945                              tr_bencDictAddInt( sargs, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi(optarg) );
1946                          break;
1947                default:  assert( "unhandled value" && 0 );
1948                          break;
1949            }
1950        }
1951        else if( stepMode == MODE_TORRENT_SET )
1952        {
1953            tr_benc * args = ensure_tset( &tset );
1954
1955            switch( c )
1956            {
1957                case 712:
1958                    {
1959                        tr_benc * trackers = tr_bencDictAddDict( args, "trackerRemove", 1 );
1960                        tr_bencDictAddInt( trackers, "id", atoi(optarg) );
1961                        break;
1962                    }
1963                case 950: tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
1964                          tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_SINGLE );
1965                          break;
1966                case 951: tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_GLOBAL );
1967                          break;
1968                case 952: tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_UNLIMITED );
1969                          break;
1970                case 984: tr_bencDictAddBool( args, "honorsSessionLimits", TRUE );
1971                          break;
1972                case 985: tr_bencDictAddBool( args, "honorsSessionLimits", FALSE );
1973                          break;
1974                default:  assert( "unhandled value" && 0 );
1975                          break;
1976            }
1977        }
1978        else if( stepMode == ( MODE_TORRENT_SET | MODE_TORRENT_ADD ) )
1979        {
1980            tr_benc * args;
1981
1982            if( tadd )
1983                args = tr_bencDictFind( tadd, ARGUMENTS );
1984            else
1985                args = ensure_tset( &tset );
1986       
1987            switch( c )
1988            {         
1989                case 'g': addFiles( args, "files-wanted", optarg );
1990                          break;
1991                case 'G': addFiles( args, "files-unwanted", optarg );
1992                          break;
1993                case 900: addFiles( args, "priority-high", optarg );
1994                          break;
1995                case 901: addFiles( args, "priority-normal", optarg );
1996                          break;
1997                case 902: addFiles( args, "priority-low", optarg );
1998                          break;
1999                case 700: tr_bencDictAddInt( args, "bandwidthPriority",  1 );
2000                          break;
2001                case 701: tr_bencDictAddInt( args, "bandwidthPriority",  0 );
2002                          break;
2003                case 702: tr_bencDictAddInt( args, "bandwidthPriority", -1 );
2004                          break;
2005                case 710:
2006                     {
2007                         tr_benc * trackers = tr_bencDictAddDict( args, "trackerAdd", 1 );
2008                         tr_bencDictAddStr( trackers, "announce", optarg );
2009                         break;
2010                     }
2011                default:  assert( "unhandled value" && 0 );
2012                          break;
2013            }
2014        }
2015        else if( c == 961 ) /* set location */
2016        {
2017            if( tadd )
2018            {
2019                tr_benc * args = tr_bencDictFind( tadd, ARGUMENTS );
2020                tr_bencDictAddStr( args, "download-dir", optarg );
2021            }
2022            else
2023            {
2024                tr_benc * args;
2025                tr_benc * top = tr_new0( tr_benc, 1 );
2026                tr_bencInitDict( top, 2 );
2027                tr_bencDictAddStr( top, "method", "torrent-set-location" );
2028                args = tr_bencDictAddDict( top, ARGUMENTS, 3 );
2029                tr_bencDictAddStr( args, "location", optarg );
2030                tr_bencDictAddBool( args, "move", FALSE );
2031                addIdArg( args, id );
2032                status |= flush( host, port, &top );
2033                break;
2034            }
2035        }
2036        else switch( c )
2037        {
2038            case 920: /* session-info */
2039            {
2040                tr_benc * top = tr_new0( tr_benc, 1 );
2041                tr_bencInitDict( top, 2 );
2042                tr_bencDictAddStr( top, "method", "session-get" );
2043                tr_bencDictAddInt( top, "tag", TAG_SESSION );
2044                status |= flush( host, port, &top );
2045                break;
2046            }
2047            case 's': /* start */
2048            {
2049                if( tadd )
2050                    tr_bencDictAddBool( tr_bencDictFind( tadd, "arguments" ), "paused", FALSE );
2051                else {
2052                    tr_benc * top = tr_new0( tr_benc, 1 );
2053                    tr_bencInitDict( top, 2 );
2054                    tr_bencDictAddStr( top, "method", "torrent-start" );
2055                    addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
2056                    status |= flush( host, port, &top );
2057                }
2058                break;
2059            }
2060            case 'S': /* stop */
2061            {
2062                if( tadd )
2063                    tr_bencDictAddBool( tr_bencDictFind( tadd, "arguments" ), "paused", TRUE );
2064                else {
2065                    tr_benc * top = tr_new0( tr_benc, 1 );
2066                    tr_bencInitDict( top, 2 );
2067                    tr_bencDictAddStr( top, "method", "torrent-stop" );
2068                    addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
2069                    status |= flush( host, port, &top );
2070                }
2071                break;
2072            }
2073            case 'w':
2074            {
2075                char * path = absolutify( optarg );
2076                if( tadd )
2077                    tr_bencDictAddStr( tr_bencDictFind( tadd, "arguments" ), "download-dir", path );
2078                else {
2079                    tr_benc * args = ensure_sset( &sset );
2080                    tr_bencDictAddStr( args, "download-dir", path );
2081                }
2082                tr_free( path );
2083                break;
2084            }
2085            case 963:
2086            {
2087                tr_benc * top = tr_new0( tr_benc, 1 );
2088                tr_bencInitDict( top, 1 );
2089                tr_bencDictAddStr( top, "method", "blocklist-update" );
2090                status |= flush( host, port, &top );
2091                break;
2092            }
2093            case 921:
2094            {
2095                tr_benc * top = tr_new0( tr_benc, 1 );
2096                tr_bencInitDict( top, 2 );
2097                tr_bencDictAddStr( top, "method", "session-stats" );
2098                tr_bencDictAddInt( top, "tag", TAG_STATS );
2099                status |= flush( host, port, &top );
2100                break;
2101            }
2102            case 962:
2103            {
2104                tr_benc * top = tr_new0( tr_benc, 1 );
2105                tr_bencInitDict( top, 2 );
2106                tr_bencDictAddStr( top, "method", "port-test" );
2107                tr_bencDictAddInt( top, "tag", TAG_PORTTEST );
2108                status |= flush( host, port, &top );
2109                break;
2110            }
2111            case 'v':
2112            {
2113                tr_benc * top;
2114                if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
2115                top = tr_new0( tr_benc, 1 );
2116                tr_bencInitDict( top, 2 );
2117                tr_bencDictAddStr( top, "method", "torrent-verify" );
2118                addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
2119                status |= flush( host, port, &top );
2120                break;
2121            }
2122            case 'r':
2123            case 'R':
2124            {
2125                tr_benc * args;
2126                tr_benc * top = tr_new0( tr_benc, 1 );
2127                tr_bencInitDict( top, 2 );
2128                tr_bencDictAddStr( top, "method", "torrent-remove" );
2129                args = tr_bencDictAddDict( top, ARGUMENTS, 2 );
2130                tr_bencDictAddBool( args, "delete-local-data", c=='R' );
2131                addIdArg( args, id );
2132                status |= flush( host, port, &top );
2133                break;
2134            }
2135            case 960:
2136            {
2137                tr_benc * args;
2138                tr_benc * top = tr_new0( tr_benc, 1 );
2139                tr_bencInitDict( top, 2 );
2140                tr_bencDictAddStr( top, "method", "torrent-set-location" );
2141                args = tr_bencDictAddDict( top, ARGUMENTS, 3 );
2142                tr_bencDictAddStr( args, "location", optarg );
2143                tr_bencDictAddBool( args, "move", TRUE );
2144                addIdArg( args, id );
2145                status |= flush( host, port, &top );
2146                break;
2147            }
2148            default:
2149            {
2150                fprintf( stderr, "got opt [%d]\n", (int)c );
2151                showUsage( );
2152                break;
2153            }
2154        }
2155    }
2156
2157    if( tadd != 0 ) status |= flush( host, port, &tadd );
2158    if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
2159    if( sset != 0 ) status |= flush( host, port, &sset );
2160    return status;
2161}
2162
2163/* [host:port] or [host] or [port] */
2164static void
2165getHostAndPort( int * argc, char ** argv, char ** host, int * port )
2166{
2167    if( *argv[1] != '-' )
2168    {
2169        int          i;
2170        const char * s = argv[1];
2171        const char * delim = strchr( s, ':' );
2172        if( delim )   /* user passed in both host and port */
2173        {
2174            *host = tr_strndup( s, delim - s );
2175            *port = atoi( delim + 1 );
2176        }
2177        else
2178        {
2179            char *    end;
2180            const int i = strtol( s, &end, 10 );
2181            if( !*end ) /* user passed in a port */
2182                *port = i;
2183            else /* user passed in a host */
2184                *host = tr_strdup( s );
2185        }
2186
2187        *argc -= 1;
2188        for( i = 1; i < *argc; ++i )
2189            argv[i] = argv[i + 1];
2190    }
2191}
2192
2193int
2194main( int argc, char ** argv )
2195{
2196    int port = DEFAULT_PORT;
2197    char * host = NULL;
2198    int exit_status = EXIT_SUCCESS;
2199
2200    if( argc < 2 ) {
2201        showUsage( );
2202        return EXIT_FAILURE;
2203    }
2204
2205    tr_formatter_mem_init( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR );
2206    tr_formatter_size_init( DISK_K,DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
2207    tr_formatter_speed_init( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
2208
2209    getHostAndPort( &argc, argv, &host, &port );
2210    if( host == NULL )
2211        host = tr_strdup( DEFAULT_HOST );
2212
2213    exit_status = processArgs( host, port, argc, (const char**)argv );
2214
2215    tr_free( host );
2216    return exit_status;
2217}
Note: See TracBrowser for help on using the repository browser.