source: trunk/daemon/remote.c @ 10646

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

(trunk daemon) #3060 "local peer discovery" -- fix bugs reported by Eszet (thanks Eszet :)

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