source: trunk/daemon/remote.c @ 10937

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

(trunk) #3045 "speed units" -- change the public API of libtransmission based on feedback from livings

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