source: trunk/daemon/remote.c @ 8908

Last change on this file since 8908 was 8908, checked in by charles, 13 years ago

(trunk daemon) #2328: new enhancement: allow transmission-remote to query the daemon for stats (Waldorf)

  • Property svn:keywords set to Date Rev Author Id
File size: 55.8 KB
Line 
1/*
2 * This file Copyright (C) 2008-2009 Charles Kerr <charles@transmissionbt.com>
3 *
4 * This file is licensed by the GPL version 2.  Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: remote.c 8908 2009-08-13 03:54:54Z charles $
11 */
12
13#include <ctype.h> /* isspace */
14#include <errno.h>
15#include <math.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h> /* strcmp */
19
20#ifdef WIN32
21 #include <direct.h> /* getcwd */
22#else
23 #include <unistd.h> /* getcwd */
24#endif
25
26#include <event.h>
27
28#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
29#include <curl/curl.h>
30
31#include <libtransmission/transmission.h>
32#include <libtransmission/bencode.h>
33#include <libtransmission/rpcimpl.h>
34#include <libtransmission/json.h>
35#include <libtransmission/tr-getopt.h>
36#include <libtransmission/utils.h>
37#include <libtransmission/version.h>
38
39#define MY_NAME "transmission-remote"
40#define DEFAULT_HOST "localhost"
41#define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR)
42
43enum { TAG_SESSION, TAG_STATS, TAG_LIST, TAG_DETAILS, TAG_FILES, TAG_PEERS };
44
45static const char*
46getUsage( void )
47{
48    return
49        "Transmission " LONG_VERSION_STRING
50        "  http://www.transmissionbt.com/\n"
51        "A fast and easy BitTorrent client\n"
52        "\n"
53        "Usage: " MY_NAME
54        " [host] [options]\n"
55        "       "
56        MY_NAME " [port] [options]\n"
57                "       "
58        MY_NAME " [host:port] [options]\n"
59                "\n"
60                "See the man page for detailed explanations and many examples.";
61}
62
63static tr_option opts[] =
64{
65    { 'a', "add",                  "Add torrent files by filename or URL", "a",  0, NULL },
66    { 'b', "debug",                "Print debugging information", "b",  0, NULL },
67    { 'd', "downlimit",            "Set the maximum global download speed in KB/s", "d",  1, "<speed>" },
68    { 'D', "no-downlimit",         "Don't limit the global download speed", "D",  0, NULL },
69    { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL },
70    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
71    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
72    { 'f', "files",                "List the current torrent(s)' files", "f",  0, NULL },
73    { 'g', "get",                  "Mark files for download", "g",  1, "<files>" },
74    { 'G', "no-get",               "Mark files for not downloading", "G",  1, "<files>" },
75    { 'i', "info",                 "Show the current torrent(s)' details", "i",  0, NULL },
76    { 920, "session-info",         "Show the session's details", "si", 0, NULL },
77    { 921, "session-stats",        "Show the session's statistics", "st", 0, NULL },
78    { 'l', "list",                 "List all torrents", "l",  0, NULL },
79    { 960, "move",                 "Move current torrent's data to a new folder", NULL, 1, "<path>" },
80    { 961, "find",                 "Tell Transmission where to find a torrent's data", NULL, 1, "<path>" },
81    { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP", "m",  0, NULL },
82    { 'M', "no-portmap",           "Disable portmapping", "M",  0, NULL },
83    { 'n', "auth",                 "Set authentication info", "n",  1, "<username:password>" },
84    { 'N', "netrc",                "Set authentication info from a .netrc file", "N",  1, "<filename>" },
85    { 'o', "dht",                  "Enable distributed hash tables (DHT)", "o", 0, NULL },
86    { 'O', "no-dht",               "Disable distributed hash tables (DHT)", "O", 0, NULL },
87    { 'p', "port",                 "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
88    { 'P', "random-port",          "Random port for incomping peers", "P", 0, NULL },
89    { 900, "priority-high",        "Set the files' priorities as high", "ph", 1, "<files>" },
90    { 901, "priority-normal",      "Set the files' priorities as normal", "pn", 1, "<files>" },
91    { 902, "priority-low",         "Set the files' priorities as low", "pl", 1, "<files>" },
92    { 'r', "remove",               "Remove the current torrent(s)", "r",  0, NULL },
93    { 930, "peers",                "Set the current torrent(s)' maximum number of peers each", "pr", 1, "<max>" },
94    { 931, "global-peers",         "Set the global maximum number of peers", "gpr", 1, "<max>" },
95    { 'R', "remove-and-delete",    "Remove the current torrent(s) and delete local data", NULL, 0, NULL },
96    { 950, "seedratio",            "Let the current torrent(s) seed until a specific ratio", "sr", 1, "ratio" },
97    { 951, "seedratio-default",    "Let the current torrent(s) use the global seedratio settings", "srd", 0, NULL },
98    { 952, "no-seedratio",         "Let the current torrent(s) seed regardless of ratio", "SR", 0, NULL },
99    { 953, "global-seedratio",     "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio", "gsr", 1, "ratio" },
100    { 954, "no-global-seedratio",  "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio", "GSR", 0, NULL },
101    { 's', "start",                "Start the current torrent(s)", "s",  0, NULL },
102    { 'S', "stop",                 "Stop the current torrent(s)", "S",  0, NULL },
103    { 't', "torrent",              "Set the current torrent(s)", "t",  1, "<torrent>" },
104    { 'u', "uplimit",              "Set the maximum global upload speed in KB/s", "u",  1, "<speed>" },
105    { 'U', "no-uplimit",           "Don't limit the global upload speed", "U",  0, NULL },
106    { 'v', "verify",               "Verify the current torrent(s)", "v",  0, NULL },
107    { 'V', "version",              "Show version number and exit", "V", 0, NULL },
108    { 'w', "download-dir",         "Set the default download folder", "w",  1, "<path>" },
109    { 'x', "pex",                  "Enable peer exchange (PEX)", "x",  0, NULL },
110    { 'X', "no-pex",               "Disable peer exchange (PEX)", "X",  0, NULL },
111    { 940, "peer-info",            "List the current torrent(s)' peers", "pi",  0, NULL },
112    {   0, NULL,                   NULL, NULL, 0, NULL }
113};
114
115static void
116showUsage( void )
117{
118    tr_getopt_usage( MY_NAME, getUsage( ), opts );
119}
120
121static int
122numarg( const char * arg )
123{
124    char *     end = NULL;
125    const long num = strtol( arg, &end, 10 );
126
127    if( *end )
128    {
129        fprintf( stderr, "Not a number: \"%s\"\n", arg );
130        showUsage( );
131        exit( EXIT_FAILURE );
132    }
133    return num;
134}
135
136static char * reqs[256]; /* arbitrary max */
137static int    reqCount = 0;
138static int    debug = 0;
139static char * auth = NULL;
140static char * netrc = NULL;
141static char * sessionId = NULL;
142
143static char*
144tr_getcwd( void )
145{
146    char buf[2048];
147    *buf = '\0';
148#ifdef WIN32
149    _getcwd( buf, sizeof( buf ) );
150#else
151    getcwd( buf, sizeof( buf ) );
152#endif
153    return tr_strdup( buf );
154}
155
156static char*
157absolutify( const char * path )
158{
159    char * buf;
160
161    if( *path == '/' )
162        buf = tr_strdup( path );
163    else {
164        char * cwd = tr_getcwd( );
165        buf = tr_buildPath( cwd, path, NULL );
166        tr_free( cwd );
167    }
168
169    return buf;
170}
171
172static char*
173getEncodedMetainfo( const char * filename )
174{
175    size_t    len = 0;
176    char *    b64 = NULL;
177    uint8_t * buf = tr_loadFile( filename, &len );
178
179    if( buf )
180    {
181        b64 = tr_base64_encode( buf, len, NULL );
182        tr_free( buf );
183    }
184    return b64;
185}
186
187static void
188addIdArg( tr_benc *    args,
189          const char * id )
190{
191    if( !*id )
192    {
193        fprintf(
194            stderr,
195            "No torrent specified!  Please use the -t option first.\n" );
196        id = "-1"; /* no torrent will have this ID, so should be a no-op */
197    }
198    if( strcmp( id, "all" ) )
199    {
200        const char * pch;
201        tr_bool isList = strchr(id,',') || strchr(id,'-');
202        tr_bool isNum = TRUE;
203        for( pch=id; isNum && *pch; ++pch )
204            if( !isdigit( *pch ) )
205                isNum = FALSE;
206        if( isNum || isList )
207            tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen( id ) );
208        else
209            tr_bencDictAddStr( args, "ids", id ); /* it's a torrent sha hash */
210    }
211}
212
213static void
214addFiles( tr_benc *    args,
215          const char * key,
216          const char * arg )
217{
218    tr_benc * files = tr_bencDictAddList( args, key, 100 );
219
220    if( !*arg )
221    {
222        fprintf( stderr, "No files specified!\n" );
223        arg = "-1"; /* no file will have this index, so should be a no-op */
224    }
225    if( strcmp( arg, "all" ) )
226    {
227        int i;
228        int valueCount;
229        int * values = tr_parseNumberRange( arg, -1, &valueCount );
230        for( i=0; i<valueCount; ++i )
231            tr_bencListAddInt( files, values[i] );
232        tr_free( values );
233    }
234}
235
236#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
237
238static const char * files_keys[] = {
239    "files",
240    "name",
241    "priorities",
242    "wanted"
243};
244
245static const char * details_keys[] = {
246    "activityDate",
247    "addedDate",
248    "announceResponse",
249    "announceURL",
250    "comment",
251    "corruptEver",
252    "creator",
253    "dateCreated",
254    "doneDate",
255    "downloadDir",
256    "downloadedEver",
257    "error",
258    "errorString",
259    "eta",
260    "hashString",
261    "haveUnchecked",
262    "haveValid",
263    "id",
264    "isPrivate",
265    "lastAnnounceTime",
266    "lastScrapeTime",
267    "leechers",
268    "leftUntilDone",
269    "name",
270    "nextAnnounceTime",
271    "nextScrapeTime",
272    "peersConnected",
273    "peersGettingFromUs",
274    "peersSendingToUs",
275    "pieceCount",
276    "pieceSize",
277    "rateDownload",
278    "rateUpload",
279    "recheckProgress",
280    "scrapeResponse",
281    "seeders",
282    "sizeWhenDone",
283    "startDate",
284    "status",
285    "timesCompleted",
286    "totalSize",
287    "uploadedEver",
288    "pieces",
289    "webseeds",
290    "webseedsSendingToUs"
291};
292
293static const char * list_keys[] = {
294    "error",
295    "errorString",
296    "eta",
297    "id",
298    "leftUntilDone",
299    "name",
300    "peersGettingFromUs",
301    "peersSendingToUs",
302    "rateDownload",
303    "rateUpload",
304    "sizeWhenDone",
305    "status",
306    "uploadRatio"
307};
308
309static int
310readargs( int           argc,
311          const char ** argv )
312{
313    int c;
314    int addingTorrents = 0;
315    int status = EXIT_SUCCESS;
316    char id[4096];
317    const char * optarg;
318
319    *id = '\0';
320
321    while( ( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg ) ) )
322    {
323        int     i, n;
324        int     addArg = TRUE;
325        tr_benc top, *args, *fields;
326        tr_bencInitDict( &top, 3 );
327        args = tr_bencDictAddDict( &top, "arguments", 0 );
328
329        switch( c )
330        {
331            case TR_OPT_UNK:
332                if( addingTorrents )
333                {
334                    char * tmp = getEncodedMetainfo( optarg );
335                    if( tmp )
336                    {
337                        tr_bencDictAddStr( &top, "method", "torrent-add" );
338                        tr_bencDictAddStr( args, "metainfo", tmp );
339                        tr_free( tmp );
340                    }
341                    else
342                    {
343                        tr_bencDictAddStr( &top, "method", "torrent-add" );
344                        tr_bencDictAddStr( args, "filename", optarg );
345                    }
346                }
347                else
348                {
349                    fprintf( stderr, "Unknown option: %s\n", optarg );
350                    addArg = FALSE;
351                    status |= EXIT_FAILURE;
352                }
353                break;
354
355            case 'a':
356                addingTorrents = 1;
357                addArg = FALSE;
358                break;
359
360            case 'b':
361                debug = 1;
362                addArg = FALSE;
363                break;
364
365            case 'd':
366                tr_bencDictAddStr( &top, "method", "session-set" );
367                tr_bencDictAddInt( args, TR_PREFS_KEY_DSPEED, numarg( optarg ) );
368                tr_bencDictAddBool( args, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
369                break;
370
371            case 'D':
372                tr_bencDictAddStr( &top, "method", "session-set" );
373                tr_bencDictAddBool( args, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
374                break;
375
376            case 'f':
377                tr_bencDictAddStr( &top, "method", "torrent-get" );
378                tr_bencDictAddInt( &top, "tag", TAG_FILES );
379                addIdArg( args, id );
380                n = TR_N_ELEMENTS( files_keys );
381                fields = tr_bencDictAddList( args, "fields", n );
382                for( i = 0; i < n; ++i )
383                    tr_bencListAddStr( fields, files_keys[i] );
384                break;
385
386            case 'g':
387                tr_bencDictAddStr( &top, "method", "torrent-set" );
388                addIdArg( args, id );
389                addFiles( args, "files-wanted", optarg );
390                break;
391
392            case 'G':
393                tr_bencDictAddStr( &top, "method", "torrent-set" );
394                addIdArg( args, id );
395                addFiles( args, "files-unwanted", optarg );
396                break;
397
398            case 'i':
399                tr_bencDictAddStr( &top, "method", "torrent-get" );
400                tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
401                addIdArg( args, id );
402                n = TR_N_ELEMENTS( details_keys );
403                fields = tr_bencDictAddList( args, "fields", n );
404                for( i = 0; i < n; ++i )
405                    tr_bencListAddStr( fields, details_keys[i] );
406                break;
407
408            case 'l':
409                tr_bencDictAddStr( &top, "method", "torrent-get" );
410                tr_bencDictAddInt( &top, "tag", TAG_LIST );
411                n = TR_N_ELEMENTS( list_keys );
412                fields = tr_bencDictAddList( args, "fields", n );
413                for( i = 0; i < n; ++i )
414                    tr_bencListAddStr( fields, list_keys[i] );
415                break;
416
417            case 'm':
418                tr_bencDictAddStr( &top, "method", "session-set" );
419                tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
420                break;
421
422            case 'M':
423                tr_bencDictAddStr( &top, "method", "session-set" );
424                tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
425                break;
426
427            case 'n':
428                auth = tr_strdup( optarg );
429                addArg = FALSE;
430                break;
431
432            case 'N':
433                netrc = tr_strdup( optarg );
434                addArg = FALSE;
435                break;
436
437            case 'p':
438                tr_bencDictAddStr( &top, "method", "session-set" );
439                tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_PORT, numarg( optarg ) );
440                break;
441
442            case 'P':
443                tr_bencDictAddStr( &top, "method", "session-set" );
444                tr_bencDictAddBool( args, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, TRUE);
445                break;
446
447            case 'r':
448                tr_bencDictAddStr( &top, "method", "torrent-remove" );
449                addIdArg( args, id );
450                break;
451
452            case 'R':
453                tr_bencDictAddStr( &top, "method", "torrent-remove" );
454                addIdArg( args, id );
455                tr_bencDictAddBool( args, "delete-local-data", TRUE );
456                break;
457
458            case 's':
459                tr_bencDictAddStr( &top, "method", "torrent-start" );
460                addIdArg( args, id );
461                break;
462
463            case 'S':
464                tr_bencDictAddStr( &top, "method", "torrent-stop" );
465                addIdArg( args, id );
466                break;
467
468            case 't':
469                tr_strlcpy( id, optarg, sizeof( id ) );
470                addArg = FALSE;
471                break;
472
473            case 'u':
474                tr_bencDictAddStr( &top, "method", "session-set" );
475                tr_bencDictAddInt( args, TR_PREFS_KEY_USPEED, numarg( optarg ) );
476                tr_bencDictAddBool( args, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
477                break;
478
479            case 'U':
480                tr_bencDictAddStr( &top, "method", "session-set" );
481                tr_bencDictAddBool( args, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
482                break;
483
484            case 'v':
485                tr_bencDictAddStr( &top, "method", "torrent-verify" );
486                addIdArg( args, id );
487                break;
488
489            case 'V':
490                fprintf( stderr, "Transmission %s\n", LONG_VERSION_STRING );
491                exit( 0 );
492                break;
493
494            case 'w': {
495                char * path = absolutify( optarg );
496                tr_bencDictAddStr( &top, "method", "session-set" );
497                tr_bencDictAddStr( args, TR_PREFS_KEY_DOWNLOAD_DIR, path );
498                tr_free( path );
499                break;
500            }
501
502            case 'o':
503                tr_bencDictAddStr( &top, "method", "session-set" );
504                tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, TRUE );
505                break;
506
507            case 'O':
508                tr_bencDictAddStr( &top, "method", "session-set" );
509                tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, FALSE );
510                break;
511
512            case 'x':
513                tr_bencDictAddStr( &top, "method", "session-set" );
514                tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, TRUE );
515                break;
516
517            case 'X':
518                tr_bencDictAddStr( &top, "method", "session-set" );
519                tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, FALSE );
520                break;
521
522            case 900:
523                tr_bencDictAddStr( &top, "method", "torrent-set" );
524                addIdArg( args, id );
525                addFiles( args, "priority-high", optarg );
526                break;
527
528            case 901:
529                tr_bencDictAddStr( &top, "method", "torrent-set" );
530                addIdArg( args, id );
531                addFiles( args, "priority-normal", optarg );
532                break;
533
534            case 902:
535                tr_bencDictAddStr( &top, "method", "torrent-set" );
536                addIdArg( args, id );
537                addFiles( args, "priority-low", optarg );
538                break;
539
540            case 910:
541                tr_bencDictAddStr( &top, "method", "session-set" );
542                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "required" );
543                break;
544
545            case 911:
546                tr_bencDictAddStr( &top, "method", "session-set" );
547                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "preferred" );
548                break;
549
550            case 912:
551                tr_bencDictAddStr( &top, "method", "session-set" );
552                tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "tolerated" );
553                break;
554
555            case 920:
556                tr_bencDictAddStr( &top, "method", "session-get" );
557                tr_bencDictAddInt( &top, "tag", TAG_SESSION );
558                break;
559
560            case 921:
561                tr_bencDictAddStr( &top, "method", "session-stats" );
562                tr_bencDictAddInt( &top, "tag", TAG_STATS );
563                break;
564               
565            case 930:
566                tr_bencDictAddStr( &top, "method", "torrent-set" );
567                addIdArg( args, id );
568                tr_bencDictAddInt( args, "peer-limit", atoi(optarg) );
569                break;
570
571            case 931:
572                tr_bencDictAddStr( &top, "method", "session-set" );
573                tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi(optarg) );
574                break;
575
576            case 940:
577                tr_bencDictAddStr( &top, "method", "torrent-get" );
578                tr_bencDictAddInt( &top, "tag", TAG_PEERS );
579                addIdArg( args, id );
580                fields = tr_bencDictAddList( args, "fields", 1 );
581                tr_bencListAddStr( fields, "peers" );
582                break;
583
584            case 950:
585                tr_bencDictAddStr( &top, "method", "torrent-set" );
586                tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
587                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_SINGLE );
588                addIdArg( args, id );
589                break;
590
591            case 951:
592                tr_bencDictAddStr( &top, "method", "torrent-set" );
593                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_GLOBAL );
594                addIdArg( args, id );
595                break;
596
597            case 952:
598                tr_bencDictAddStr( &top, "method", "torrent-set" );
599                tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_UNLIMITED );
600                addIdArg( args, id );
601                break;
602
603            case 953:
604                tr_bencDictAddStr( &top, "method", "session-set" );
605                tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
606                tr_bencDictAddBool( args, "seedRatioLimited", TRUE );
607                break;
608
609            case 954:
610                tr_bencDictAddStr( &top, "method", "session-set" );
611                tr_bencDictAddBool( args, "seedRatioLimited", FALSE );
612                break;
613
614            case 960:
615                tr_bencDictAddStr( &top, "method", "torrent-set-location" );
616                tr_bencDictAddStr( args, "location", optarg );
617                tr_bencDictAddBool( args, "move", TRUE );
618                addIdArg( args, id );
619                break;
620
621            case 961:
622                tr_bencDictAddStr( &top, "method", "torrent-set-location" );
623                tr_bencDictAddStr( args, "location", optarg );
624                tr_bencDictAddBool( args, "move", FALSE );
625                addIdArg( args, id );
626                break;
627
628            case TR_OPT_ERR:
629                fprintf( stderr, "invalid option\n" );
630                showUsage( );
631                status |= EXIT_FAILURE;
632                break;
633
634            default:
635                fprintf( stderr, "got opt [%d]\n", (int)c );
636                showUsage( );
637                break;
638        }
639
640        if( addArg )
641        {
642            reqs[reqCount++] = tr_bencToStr( &top, TR_FMT_JSON_LEAN, NULL );
643        }
644
645        tr_bencFree( &top );
646    }
647
648    return status;
649}
650
651/* [host:port] or [host] or [port] */
652static void
653getHostAndPort( int * argc, char ** argv, char ** host, int * port )
654{
655    if( *argv[1] != '-' )
656    {
657        int          i;
658        const char * s = argv[1];
659        const char * delim = strchr( s, ':' );
660        if( delim )   /* user passed in both host and port */
661        {
662            *host = tr_strndup( s, delim - s );
663            *port = atoi( delim + 1 );
664        }
665        else
666        {
667            char *    end;
668            const int i = strtol( s, &end, 10 );
669            if( !*end ) /* user passed in a port */
670                *port = i;
671            else /* user passed in a host */
672                *host = tr_strdup( s );
673        }
674
675        *argc -= 1;
676        for( i = 1; i < *argc; ++i )
677            argv[i] = argv[i + 1];
678    }
679}
680
681static size_t
682writeFunc( void * ptr,
683           size_t size,
684           size_t nmemb,
685           void * buf )
686{
687    const size_t byteCount = size * nmemb;
688
689    evbuffer_add( buf, ptr, byteCount );
690    return byteCount;
691}
692
693static char*
694tr_strltime( char * buf, int seconds, size_t buflen )
695{
696    int  days, hours, minutes;
697    char d[128], h[128], m[128], s[128];
698
699    if( seconds < 0 )
700        seconds = 0;
701
702    days = seconds / 86400;
703    hours = ( seconds % 86400 ) / 3600;
704    minutes = ( seconds % 3600 ) / 60;
705    seconds = ( seconds % 3600 ) % 60;
706
707    tr_snprintf( d, sizeof( d ), ngettext( "%'d day", "%'d days", days ), days );
708    tr_snprintf( h, sizeof( h ), ngettext( "%'d hour", "%'d hours", hours ), hours );
709    tr_snprintf( m, sizeof( m ), ngettext( "%'d minute", "%'d minutes", minutes ), minutes );
710    tr_snprintf( s, sizeof( s ), ngettext( "%'d second", "%'d seconds", seconds ), seconds );
711
712    if( days )
713    {
714        if( days >= 4 || !hours )
715            tr_strlcpy( buf, d, buflen );
716        else
717            tr_snprintf( buf, buflen, "%s, %s", d, h );
718    }
719    else if( hours )
720    {
721        if( hours >= 4 || !minutes )
722            tr_strlcpy( buf, h, buflen );
723        else
724            tr_snprintf( buf, buflen, "%s, %s", h, m );
725    }
726    else if( minutes )
727    {
728        if( minutes >= 4 || !seconds )
729            tr_strlcpy( buf, m, buflen );
730        else
731            tr_snprintf( buf, buflen, "%s, %s", m, s );
732    }
733    else tr_strlcpy( buf, s, buflen );
734
735    return buf;
736}
737
738#define KILOBYTE_FACTOR 1024.0
739#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
740#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
741
742static char*
743strlratio2( char * buf, double ratio, size_t buflen )
744{
745    return tr_strratio( buf, buflen, ratio, "Inf" );
746}
747
748static char*
749strlratio( char * buf,
750           double numerator,
751           double denominator,
752           size_t buflen )
753{
754    double ratio;
755
756    if( denominator )
757        ratio = numerator / denominator;
758    else if( numerator )
759        ratio = TR_RATIO_INF;
760    else
761        ratio = TR_RATIO_NA;
762
763    return strlratio2( buf, ratio, buflen );
764}
765
766static char*
767strlsize( char *  buf, int64_t size, size_t  buflen )
768{
769    if( !size )
770        tr_strlcpy( buf, "None", buflen );
771    else if( size < (int64_t)KILOBYTE_FACTOR )
772        tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
773    else
774    {
775        double displayed_size;
776        if( size < (int64_t)MEGABYTE_FACTOR )
777        {
778            displayed_size = (double) size / KILOBYTE_FACTOR;
779            tr_snprintf( buf, buflen, "%'.1f KB", displayed_size );
780        }
781        else if( size < (int64_t)GIGABYTE_FACTOR )
782        {
783            displayed_size = (double) size / MEGABYTE_FACTOR;
784            tr_snprintf( buf, buflen, "%'.1f MB", displayed_size );
785        }
786        else
787        {
788            displayed_size = (double) size / GIGABYTE_FACTOR;
789            tr_snprintf( buf, buflen, "%'.1f GB", displayed_size );
790        }
791    }
792    return buf;
793}
794
795static char*
796getStatusString( tr_benc * t, char * buf, size_t buflen )
797{
798    int64_t status;
799
800    if( !tr_bencDictFindInt( t, "status", &status ) )
801    {
802        *buf = '\0';
803    }
804    else switch( status )
805    {
806        case TR_STATUS_STOPPED:
807            tr_strlcpy( buf, "Stopped", buflen );
808            break;
809
810        case TR_STATUS_CHECK_WAIT:
811        case TR_STATUS_CHECK: {
812            const char * str = status == TR_STATUS_CHECK_WAIT
813                             ? "Will Verify"
814                             : "Verifying";
815            double percent;
816            if( tr_bencDictFindReal( t, "recheckProgress", &percent ) )
817                tr_snprintf( buf, buflen, "%s (%.0f%%)", str, floor(percent*100.0) );
818            else
819                tr_strlcpy( buf, str, buflen );
820
821            break;
822        }
823
824        case TR_STATUS_DOWNLOAD:
825        case TR_STATUS_SEED: {
826            int64_t fromUs = 0;
827            int64_t toUs = 0;
828            tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
829            tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
830            if( fromUs && toUs )
831                tr_strlcpy( buf, "Up & Down", buflen );
832            else if( toUs )
833                tr_strlcpy( buf, "Downloading", buflen );
834            else if( fromUs ) {
835                int64_t leftUntilDone = 0;
836                tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
837                if( leftUntilDone > 0 )
838                    tr_strlcpy( buf, "Uploading", buflen );
839                else
840                    tr_strlcpy( buf, "Seeding", buflen );
841            } else {
842                tr_strlcpy( buf, "Idle", buflen );
843            }
844            break;
845        }
846    }
847
848    return buf;
849}
850
851static const char*
852getTrackerDateStr( const time_t t, tr_bool isStopped )
853{
854    const char * str;
855    switch( t ) {
856        case 0: str = isStopped ? "None (Stopped)\n" : "None\n"; break;
857        case 1: str = "In Progress\n"; break;
858        default: str = ctime( &t ); break;
859    }
860    return str;
861}
862
863static void
864printSession( tr_benc * top )
865{
866    tr_benc *args;
867    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
868    {
869        const char * str;
870        int64_t      i;
871        tr_bool      boolVal;
872
873        printf( "VERSION\n" );
874        if( tr_bencDictFindStr( args,  "version", &str ) )
875            printf( "  Daemon version: %s\n", str );
876        if( tr_bencDictFindInt( args, "rpc-version", &i ) )
877            printf( "  RPC version: %" PRId64 "\n", i );
878        if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) )
879            printf( "  RPC minimum version: %" PRId64 "\n", i );
880        printf( "\n" );
881
882        printf( "TRANSFER\n" );
883        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
884            printf( "  Download directory: %s\n", str );
885        if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) )
886            printf( "  Listenport: %" PRId64 "\n", i );
887        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
888            printf( "  Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
889        if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
890            printf( "  Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) );
891        if( tr_bencDictFindStr( args,  TR_PREFS_KEY_ENCRYPTION, &str ) )
892            printf( "  Encryption: %s\n", str );
893        printf( "\n" );
894
895        {
896            tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled;
897            int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit;
898
899            if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN, &altDown ) &&
900                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) &&
901                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) &&
902                tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) &&
903                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) &&
904                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) &&
905                tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP, &altUp ) &&
906                tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) &&
907                tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED, &downLimit ) &&
908                tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) &&
909                tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED, &upLimit ) &&
910                tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) )
911            {
912                char buf[128];
913
914                printf( "LIMITS\n" );
915                printf( "  Peer limit: %" PRId64 "\n", peerLimit );
916
917                if( altEnabled )
918                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altUp );
919                else if( upEnabled )
920                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", upLimit );
921                else
922                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
923                printf( "  Upload speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
924                        buf,
925                        (upEnabled?"Enabled":"Disabled"), upLimit,
926                        (altEnabled?"Enabled":"Disabled"), altUp );
927
928                if( altEnabled )
929                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", altDown );
930                else if( downEnabled )
931                    tr_snprintf( buf, sizeof( buf ), "%"PRId64" KB/s", downLimit );
932                else
933                    tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
934                printf( "  Download speed limit: %s  (%s limit: %"PRId64" KB/s; %s turtle limit: %"PRId64" KB/s)\n",
935                        buf,
936                        (downEnabled?"Enabled":"Disabled"), downLimit,
937                        (altEnabled?"Enabled":"Disabled"), altDown );
938
939                if( altTimeEnabled ) {
940                    printf( "  Turtle schedule: %02d:%02d - %02d:%02d  ",
941                            (int)(altBegin/60), (int)(altBegin%60),
942                            (int)(altEnd/60), (int)(altEnd%60) );
943                    if( altDay & TR_SCHED_SUN )   printf( "Sun " );
944                    if( altDay & TR_SCHED_MON )   printf( "Mon " );
945                    if( altDay & TR_SCHED_TUES )  printf( "Tue " );
946                    if( altDay & TR_SCHED_WED )   printf( "Wed " );
947                    if( altDay & TR_SCHED_THURS ) printf( "Thu " );
948                    if( altDay & TR_SCHED_FRI )   printf( "Fri " );
949                    if( altDay & TR_SCHED_SAT )   printf( "Sat " );
950                    printf( "\n" );
951                }
952            }
953        }
954    }
955}
956
957static void
958printSessionStats( tr_benc * top )
959{
960    tr_benc *args, *d;
961    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
962    {
963        char buf[512];
964        int64_t up, down, secs, sessions;
965
966        if( tr_bencDictFindDict( args, "current-stats", &d )
967            && tr_bencDictFindInt( d, "uploadedBytes", &up )
968            && tr_bencDictFindInt( d, "downloadedBytes", &down )
969            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
970        {
971            printf( "\nCURRENT SESSION\n" );
972            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
973            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
974            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
975            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
976        }
977
978        if( tr_bencDictFindDict( args, "cumulative-stats", &d )
979            && tr_bencDictFindInt( d, "sessionCount", &sessions )
980            && tr_bencDictFindInt( d, "uploadedBytes", &up )
981            && tr_bencDictFindInt( d, "downloadedBytes", &down )
982            && tr_bencDictFindInt( d, "secondsActive", &secs ) )
983        {
984            printf( "\nTOTAL\n" );
985            printf( "  Started %lu times\n", (unsigned long)sessions );
986            printf( "  Uploaded:   %s\n", strlsize( buf, up, sizeof( buf ) ) );
987            printf( "  Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
988            printf( "  Ratio:      %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
989            printf( "  Duration:   %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
990        }
991    }
992}
993
994static void
995printDetails( tr_benc * top )
996{
997    tr_benc *args, *torrents;
998
999    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1000      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1001    {
1002        int ti, tCount;
1003        for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
1004             ++ti )
1005        {
1006            tr_benc *    t = tr_bencListChild( torrents, ti );
1007            tr_benc *    l;
1008            const uint8_t * raw;
1009            size_t       rawlen;
1010            const char * str;
1011            char         buf[512];
1012            char         buf2[512];
1013            int64_t      i, j, k;
1014            tr_bool      isStopped;
1015            tr_bool      boolVal;
1016
1017            isStopped = tr_bencDictFindInt( t, "status", &i ) && (i==TR_STATUS_STOPPED);
1018
1019            printf( "NAME\n" );
1020            if( tr_bencDictFindInt( t, "id", &i ) )
1021                printf( "  Id: %" PRId64 "\n", i );
1022            if( tr_bencDictFindStr( t, "name", &str ) )
1023                printf( "  Name: %s\n", str );
1024            if( tr_bencDictFindStr( t, "hashString", &str ) )
1025                printf( "  Hash: %s\n", str );
1026            printf( "\n" );
1027
1028            printf( "TRANSFER\n" );
1029            getStatusString( t, buf, sizeof( buf ) );
1030            printf( "  State: %s\n", buf );
1031
1032            if( tr_bencDictFindStr( t, "downloadDir", &str ) )
1033                printf( "  Location: %s\n", str );
1034
1035            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1036              && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
1037            {
1038                strlratio( buf, 100.0 * ( i - j ), i, sizeof( buf ) );
1039                printf( "  Percent Done: %s%%\n", buf );
1040            }
1041
1042            if( tr_bencDictFindInt( t, "eta", &i ) )
1043                printf( "  ETA: %s\n", tr_strltime( buf, i, sizeof( buf ) ) );
1044            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
1045                printf( "  Download Speed: %.1f KB/s\n", i / 1024.0 );
1046            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
1047                printf( "  Upload Speed: %.1f KB/s\n", i / 1024.0 );
1048            if( tr_bencDictFindInt( t, "haveUnchecked", &i )
1049              && tr_bencDictFindInt( t, "haveValid", &j ) )
1050            {
1051                strlsize( buf, i + j, sizeof( buf ) );
1052                strlsize( buf2, j, sizeof( buf2 ) );
1053                printf( "  Have: %s (%s verified)\n", buf, buf2 );
1054            }
1055
1056            if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
1057              && tr_bencDictFindInt( t, "totalSize", &j ) )
1058            {
1059                strlsize( buf, j, sizeof( buf ) );
1060                strlsize( buf2, i, sizeof( buf2 ) );
1061                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
1062            }
1063            if( tr_bencDictFindInt( t, "downloadedEver", &i )
1064              && tr_bencDictFindInt( t, "uploadedEver", &j ) )
1065            {
1066                strlsize( buf, i, sizeof( buf ) );
1067                printf( "  Downloaded: %s\n", buf );
1068                strlsize( buf, j, sizeof( buf ) );
1069                printf( "  Uploaded: %s\n", buf );
1070                strlratio( buf, j, i, sizeof( buf ) );
1071                printf( "  Ratio: %s\n", buf );
1072            }
1073            if( tr_bencDictFindInt( t, "corruptEver", &i ) )
1074            {
1075                strlsize( buf, i, sizeof( buf ) );
1076                printf( "  Corrupt DL: %s\n", buf );
1077            }
1078            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str &&
1079                tr_bencDictFindInt( t, "error", &i ) && i )
1080            {
1081                switch( i ) {
1082                    case TR_STAT_TRACKER_WARNING: printf( "  Tracker gave a warning: %s\n", str ); break;
1083                    case TR_STAT_TRACKER_ERROR:   printf( "  Tracker gave an error: %s\n", str ); break;
1084                    case TR_STAT_LOCAL_ERROR:     printf( "  Error: %s\n", str ); break;
1085                    default: break; /* no error */
1086                }
1087            }
1088            if( tr_bencDictFindInt( t, "peersConnected", &i )
1089              && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
1090              && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
1091            {
1092                printf(
1093                    "  Peers: "
1094                    "connected to %" PRId64 ", "
1095                                            "uploading to %" PRId64
1096                    ", "
1097                    "downloading from %"
1098                    PRId64 "\n",
1099                    i, j, k );
1100            }
1101
1102            if( tr_bencDictFindList( t, "webseeds", &l )
1103              && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
1104            {
1105                const int64_t n = tr_bencListSize( l );
1106                if( n > 0 )
1107                    printf(
1108                        "  Web Seeds: downloading from %" PRId64 " of %"
1109                        PRId64
1110                        " web seeds\n", i, n );
1111            }
1112            printf( "\n" );
1113
1114            printf( "HISTORY\n" );
1115            if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
1116            {
1117                const time_t tt = i;
1118                printf( "  Date added:      %s", ctime( &tt ) );
1119            }
1120            if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
1121            {
1122                const time_t tt = i;
1123                printf( "  Date finished:   %s", ctime( &tt ) );
1124            }
1125            if( tr_bencDictFindInt( t, "startDate", &i ) && i )
1126            {
1127                const time_t tt = i;
1128                printf( "  Date started:    %s", ctime( &tt ) );
1129            }
1130            if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
1131            {
1132                const time_t tt = i;
1133                printf( "  Latest activity: %s", ctime( &tt ) );
1134            }
1135            printf( "\n" );
1136
1137            printf( "TRACKER\n" );
1138            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) )
1139                printf( "  Latest announce: %s", getTrackerDateStr( (time_t)i, isStopped ) );
1140            if( tr_bencDictFindStr( t, "announceURL", &str ) )
1141                printf( "  Announce URL: %s\n", str );
1142            if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str )
1143                printf( "  Announce response: %s\n", str );
1144            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) )
1145                printf( "  Next announce:   %s", getTrackerDateStr( (time_t)i, isStopped ) );
1146            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) )
1147                printf( "  Latest scrape:   %s", getTrackerDateStr( (time_t)i, isStopped ) );
1148            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
1149                printf( "  Scrape response: %s\n", str );
1150            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) )
1151                printf( "  Next scrape:     %s", getTrackerDateStr( (time_t)i, isStopped ) );
1152            if( tr_bencDictFindInt( t, "seeders", &i ) && tr_bencDictFindInt( t, "leechers", &j ) )
1153                printf( "  Tracker knows of %" PRId64 " seeders and %" PRId64 " leechers\n", i, j );
1154            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
1155                printf( "  Tracker has seen %" PRId64 " clients complete this torrent\n", i );
1156            printf( "\n" );
1157
1158            printf( "ORIGINS\n" );
1159            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
1160            {
1161                const time_t tt = i;
1162                printf( "  Date created: %s", ctime( &tt ) );
1163            }
1164            if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) )
1165                printf( "  Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) );
1166            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
1167                printf( "  Comment: %s\n", str );
1168            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
1169                printf( "  Creator: %s\n", str );
1170            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
1171                printf( "  Piece Count: %" PRId64 "\n", i );
1172            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
1173                printf( "  Piece Size: %" PRId64 "\n", i );
1174            printf( "\n" );
1175
1176            printf("PIECES\n  ");
1177            if( tr_bencDictFindRaw( t, "pieces", &raw, &rawlen ) && tr_bencDictFindInt( t, "pieceCount", &j ) ) {
1178                int len;
1179                char * str = tr_base64_decode( raw, rawlen, &len );
1180                for( i=k=0; k<len; ++k ) {
1181                    int e;
1182                    for( e=0; i<j && e<8; ++e, ++i )
1183                        printf( str[k] & (1<<(7-e)) ? "1" : "0" );
1184                    printf( " " );
1185                    if( !(i%64) )
1186                        printf( "\n  " );
1187                }
1188                tr_free( str );
1189            }
1190            printf( "\n" );
1191        }
1192    }
1193}
1194
1195static void
1196printFileList( tr_benc * top )
1197{
1198    tr_benc *args, *torrents;
1199
1200    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1201      && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1202    {
1203        int i, in;
1204        for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
1205        {
1206            tr_benc *    d = tr_bencListChild( torrents, i );
1207            tr_benc *    files, *priorities, *wanteds;
1208            const char * name;
1209            if( tr_bencDictFindStr( d, "name", &name )
1210              && tr_bencDictFindList( d, "files", &files )
1211              && tr_bencDictFindList( d, "priorities", &priorities )
1212              && tr_bencDictFindList( d, "wanted", &wanteds ) )
1213            {
1214                int j = 0, jn = tr_bencListSize( files );
1215                printf( "%s (%d files):\n", name, jn );
1216                printf( "%3s  %4s %8s %3s %9s  %s\n", "#", "Done",
1217                        "Priority", "Get", "Size",
1218                        "Name" );
1219                for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1220                {
1221                    int64_t      have;
1222                    int64_t      length;
1223                    int64_t      priority;
1224                    int64_t      wanted;
1225                    const char * filename;
1226                    tr_benc *    file = tr_bencListChild( files, j );
1227                    if( tr_bencDictFindInt( file, "length", &length )
1228                      && tr_bencDictFindStr( file, "name", &filename )
1229                      && tr_bencDictFindInt( file, "bytesCompleted", &have )
1230                      && tr_bencGetInt( tr_bencListChild( priorities,
1231                                                          j ), &priority )
1232                      && tr_bencGetInt( tr_bencListChild( wanteds,
1233                                                          j ), &wanted ) )
1234                    {
1235                        char         sizestr[64];
1236                        double       percent = (double)have / length;
1237                        const char * pristr;
1238                        strlsize( sizestr, length, sizeof( sizestr ) );
1239                        switch( priority )
1240                        {
1241                            case TR_PRI_LOW:
1242                                pristr = "Low"; break;
1243
1244                            case TR_PRI_HIGH:
1245                                pristr = "High"; break;
1246
1247                            default:
1248                                pristr = "Normal"; break;
1249                        }
1250                        printf( "%3d: %3.0f%% %-8s %-3s %9s  %s\n",
1251                                j,
1252                                floor( 100.0 * percent ),
1253                                pristr,
1254                                ( wanted ? "Yes" : "No" ),
1255                                sizestr,
1256                                filename );
1257                    }
1258                }
1259            }
1260        }
1261    }
1262}
1263
1264static void
1265printPeersImpl( tr_benc * peers )
1266{
1267    int i, n;
1268    printf( "%-20s  %-12s  %-5s %-6s  %-6s  %s\n",
1269            "Address", "Flags", "Done", "Down", "Up", "Client" );
1270    for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1271    {
1272        double progress;
1273        const char * address, * client, * flagstr;
1274        int64_t rateToClient, rateToPeer;
1275        tr_benc * d = tr_bencListChild( peers, i );
1276
1277        if( tr_bencDictFindStr( d, "address", &address )
1278          && tr_bencDictFindStr( d, "clientName", &client )
1279          && tr_bencDictFindReal( d, "progress", &progress )
1280          && tr_bencDictFindStr( d, "flagStr", &flagstr )
1281          && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1282          && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1283        {
1284            printf( "%-20s  %-12s  %-5.1f %6.1f  %6.1f  %s\n",
1285                    address, flagstr, (progress*100.0),
1286                    rateToClient / 1024.0,
1287                    rateToPeer / 1024.0,
1288                    client );
1289        }
1290    }
1291}
1292
1293static void
1294printPeers( tr_benc * top )
1295{
1296    tr_benc *args, *torrents;
1297
1298    if( tr_bencDictFindDict( top, "arguments", &args )
1299      && tr_bencDictFindList( args, "torrents", &torrents ) )
1300    {
1301        int i, n;
1302        for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1303        {
1304            tr_benc * peers;
1305            tr_benc * torrent = tr_bencListChild( torrents, i );
1306            if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1307                printPeersImpl( peers );
1308                if( i+1<n )
1309                    printf( "\n" );
1310            }
1311        }
1312    }
1313}
1314
1315static void
1316printTorrentList( tr_benc * top )
1317{
1318    tr_benc *args, *list;
1319
1320    if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1321      && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1322    {
1323        int i, n;
1324        int64_t total_up = 0, total_down = 0, total_size = 0;
1325        char haveStr[32];
1326
1327        printf( "%-4s   %-4s  %9s  %-8s  %6s  %6s  %-5s  %-11s  %s\n",
1328                "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1329                "Name" );
1330
1331        for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1332        {
1333            int64_t      id, eta, status, up, down;
1334            int64_t      sizeWhenDone, leftUntilDone;
1335            double       ratio;
1336            const char * name;
1337            tr_benc *   d = tr_bencListChild( list, i );
1338            if( tr_bencDictFindInt( d, "eta", &eta )
1339              && tr_bencDictFindInt( d, "id", &id )
1340              && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1341              && tr_bencDictFindStr( d, "name", &name )
1342              && tr_bencDictFindInt( d, "rateDownload", &down )
1343              && tr_bencDictFindInt( d, "rateUpload", &up )
1344              && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1345              && tr_bencDictFindInt( d, "status", &status )
1346              && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1347            {
1348                char etaStr[16];
1349                char statusStr[64];
1350                char ratioStr[32];
1351                char doneStr[8];
1352                int64_t error;
1353                char errorMark;
1354
1355                if( sizeWhenDone )
1356                    tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1357                else
1358                    tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1359
1360                strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1361
1362                if( leftUntilDone )
1363                    tr_strltime( etaStr, eta, sizeof( etaStr ) );
1364                else
1365                    tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1366                if( tr_bencDictFindInt( d, "error", &error ) && error )
1367                    errorMark = '*';
1368                else
1369                    errorMark = ' ';
1370                printf(
1371                    "%4d%c  %4s  %9s  %-8s  %6.1f  %6.1f  %5s  %-11s  %s\n",
1372                    (int)id, errorMark,
1373                    doneStr,
1374                    haveStr,
1375                    etaStr,
1376                    up / 1024.0,
1377                    down / 1024.0,
1378                    strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1379                    getStatusString( d, statusStr, sizeof( statusStr ) ),
1380                    name );
1381
1382                total_up += up;
1383                total_down += down;
1384                total_size += sizeWhenDone - leftUntilDone;
1385            }
1386        }
1387
1388        printf( "Sum:         %9s            %6.1f  %6.1f\n",
1389                strlsize( haveStr, total_size, sizeof( haveStr ) ),
1390                total_up / 1024.0,
1391                total_down / 1024.0 );
1392    }
1393}
1394
1395static int
1396processResponse( const char * host,
1397                 int          port,
1398                 const void * response,
1399                 size_t       len )
1400{
1401    tr_benc top;
1402    int status = EXIT_SUCCESS;
1403
1404    if( debug )
1405        fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1406                 (int)len, (int)len, (int)len, (const char*) response );
1407
1408    if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1409    {
1410        tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1411                 (int)len, (char*)response );
1412        status |= EXIT_FAILURE;
1413    }
1414    else
1415    {
1416        int64_t      tag = -1;
1417        const char * str;
1418        tr_bencDictFindInt( &top, "tag", &tag );
1419
1420        switch( tag )
1421        {
1422            case TAG_SESSION:
1423                printSession( &top ); break;
1424
1425            case TAG_STATS:
1426                printSessionStats( &top ); break;
1427
1428            case TAG_FILES:
1429                printFileList( &top ); break;
1430
1431            case TAG_DETAILS:
1432                printDetails( &top ); break;
1433
1434            case TAG_LIST:
1435                printTorrentList( &top ); break;
1436
1437            case TAG_PEERS:
1438                printPeers( &top ); break;
1439
1440            default:
1441                if( !tr_bencDictFindStr( &top, "result", &str ) )
1442                    status |= EXIT_FAILURE;
1443                else {
1444                    printf( "%s:%d responded: \"%s\"\n", host, port, str );
1445                    if( strcmp( str, "success") )
1446                        status |= EXIT_FAILURE;
1447                }
1448        }
1449
1450        tr_bencFree( &top );
1451    }
1452
1453    return status;
1454}
1455
1456/* look for a session id in the header in case the server gives back a 409 */
1457static size_t
1458parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
1459{
1460    const char * line = ptr;
1461    const size_t line_len = size * nmemb;
1462    const char * key = TR_RPC_SESSION_ID_HEADER ": ";
1463    const size_t key_len = strlen( key );
1464
1465    if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
1466    {
1467        const char * begin = line + key_len;
1468        const char * end = begin;
1469        while( !isspace( *end ) )
1470            ++end;
1471        tr_free( sessionId );
1472        sessionId = tr_strndup( begin, end-begin );
1473    }
1474
1475    return line_len;
1476}
1477
1478static CURL*
1479tr_curl_easy_init( struct evbuffer * writebuf )
1480{
1481    CURL * curl = curl_easy_init( );
1482    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1483    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1484    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1485    curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1486    curl_easy_setopt( curl, CURLOPT_POST, 1 );
1487    curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1488    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1489    curl_easy_setopt( curl, CURLOPT_TIMEOUT, 60L );
1490    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1491    curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1492    if( netrc )
1493        curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1494    if( auth )
1495        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1496    if( sessionId ) {
1497        char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1498        struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1499        curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1500        /* fixme: leaks */
1501    }
1502    return curl;
1503}
1504
1505
1506static int
1507processRequests( const char *  host,
1508                 int           port,
1509                 const char ** reqs,
1510                 int           reqCount )
1511{
1512    int i;
1513    CURL * curl = NULL;
1514    struct evbuffer * buf = evbuffer_new( );
1515    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1516    int status = EXIT_SUCCESS;
1517
1518    for( i=0; i<reqCount; ++i )
1519    {
1520        CURLcode res;
1521        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
1522
1523        if( curl == NULL )
1524        {
1525            curl = tr_curl_easy_init( buf );
1526            curl_easy_setopt( curl, CURLOPT_URL, url );
1527        }
1528
1529        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
1530
1531        if( debug )
1532            fprintf( stderr, "posting:\n--------\n%s\n--------\n", reqs[i] );
1533        if( ( res = curl_easy_perform( curl ) ) )
1534        {
1535            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
1536            status |= EXIT_FAILURE;
1537        }
1538        else {
1539            long response;
1540            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1541            switch( response ) {
1542                case 200:
1543                    status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1544                    break;
1545                case 409:
1546                    /* session id failed.  our curl header func has already
1547                     * pulled the new session id from this response's headers,
1548                     * build a new CURL* and try again */
1549                    curl_easy_cleanup( curl );
1550                    curl = NULL;
1551                    --i;
1552                    break;
1553                default:
1554                    fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1555                    status |= EXIT_FAILURE;
1556                    break;
1557            }
1558        }
1559    }
1560
1561    /* cleanup */
1562    tr_free( url );
1563    evbuffer_free( buf );
1564    if( curl != NULL )
1565        curl_easy_cleanup( curl );
1566    return status;
1567}
1568
1569int
1570main( int     argc,
1571      char ** argv )
1572{
1573    int    i;
1574    int    port = DEFAULT_PORT;
1575    char * host = NULL;
1576    int    exit_status = EXIT_SUCCESS;
1577
1578    if( argc < 2 ) {
1579        showUsage( );
1580        return EXIT_FAILURE;
1581    }
1582
1583    getHostAndPort( &argc, argv, &host, &port );
1584    if( host == NULL )
1585        host = tr_strdup( DEFAULT_HOST );
1586
1587    exit_status |= readargs( argc, (const char**)argv );
1588    if( reqCount )
1589        exit_status = processRequests( host, port, (const char**)reqs, reqCount );
1590    else {
1591        showUsage( );
1592        return EXIT_FAILURE;
1593    }
1594
1595
1596    for( i=0; i<reqCount; ++i )
1597        tr_free( reqs[i] );
1598
1599    tr_free( host );
1600    return exit_status;
1601}
1602
Note: See TracBrowser for help on using the repository browser.