source: trunk/daemon/remote.c @ 10635

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

(trunk) #3060 "Local Peer Discovery" -- in the code, rename LDS as LPD for Local Peer Discovery

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