source: trunk/daemon/remote.c @ 10849

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

(trunk daemon) #3324 "transmission-remote has no output/status message" -- fixed

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