source: trunk/daemon/remote.c @ 10818

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

(trunk) add tr_formatter_size() and tr_formatter_speed() so that all the client apps don't have to reinvent the wheel

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