source: trunk/daemon/remote.c @ 10704

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

(trunk daemon) fix regression introduced in trunk after 1.93 was released: #3241 "transmission-remote doesn't return an error code"

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