source: trunk/daemon/remote.c @ 10931

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

(trunk) #3045 "make libtransmission's API byte-oriented instead of KiB-oriented." -- implemented. This is a largish commit and will break the mac build for a little while.

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