source: trunk/daemon/remote.c @ 10798

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

(trunk) #1521 "memory cache to reduce disk IO" -- commit block-cache-rc1.diff to trunk for the nightlies.

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