source: trunk/daemon/remote.c @ 6292

Last change on this file since 6292 was 6292, checked in by charles, 14 years ago

transmission-remote: add a very detailed torrent `info' command

  • Property svn:keywords set to Date Rev Author Id
File size: 28.4 KB
Line 
1/*
2 * This file Copyright (C) 2008 Charles Kerr <charles@rebelbase.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 6292 2008-07-07 05:53:15Z charles $
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <string.h> /* strcmp */
16
17#include <getopt.h>
18#include <unistd.h> /* getcwd */
19
20#include <libevent/event.h>
21#include <curl/curl.h>
22
23#include <libtransmission/transmission.h>
24#include <libtransmission/bencode.h>
25#include <libtransmission/rpc.h>
26#include <libtransmission/json.h>
27#include <libtransmission/utils.h>
28#include <libtransmission/version.h>
29
30#define MY_NAME "transmission-remote"
31#define DEFAULT_HOST "localhost"
32#define DEFAULT_PORT TR_DEFAULT_RPC_PORT
33
34enum { TAG_LIST, TAG_DETAILS, TAG_FILES, TAG_PEERS };
35
36static void
37showUsage( void )
38{
39    puts( "Transmission "LONG_VERSION_STRING"  http://www.transmissionbt.com/\n"
40            "A fast and easy BitTorrent client\n"
41            "\n"
42            "Usage: "MY_NAME" [host] [options]\n"
43            "       "MY_NAME" [port] [options]\n"
44            "       "MY_NAME" [host:port] [options]\n"
45            "\n"
46            "Options:\n"
47            "  -a --add <torrent>        Add a torrent\n"
48            "  -d --download-limit <int> Max download rate in KiB/s\n"
49            "  -D --download-unlimited   No download rate limit\n"
50            "  -e --encryption required  Require encryption for all peers\n"
51            "  -e --encryption preferred Prefer peers to use encryption\n"
52            "  -e --encryption tolerated Prefer peers to use plaintext\n"
53            "  -f --files <id>           Get a file list for the specified torrent\n"
54            "  -g --debug                Print debugging information\n"
55            "  -h --help                 Display this message and exit\n"
56            "  -i --info                 Detailed information for the specified torrent\n"
57            "  -l --list                 List all torrents\n"
58            "  -m --port-mapping         Automatic port mapping via NAT-PMP or UPnP\n"
59            "  -M --no-port-mapping      Disable automatic port mapping\n"
60            "  -p --port <id>            Port to listen for incoming peers\n"
61            "  -r --remove <id>          Remove the torrent with the given ID\n"
62            "  -r --remove all           Remove all torrents\n"
63            "  -s --start <id>           Start the torrent with the given ID\n"
64            "  -s --start all            Start all stopped torrents\n"
65            "  -S --stop <id>            Stop the torrent with the given ID\n"
66            "  -S --stop all             Stop all running torrents\n"
67            "  -t --auth <user>:<pass>   Username and password for authentication\n"
68            "  -u --upload-limit <int>   Max upload rate in KiB/s\n"
69            "  -U --upload-unlimited     No upload rate limit\n"
70            "  -v --verify <id>          Verify the torrent's local data\n"
71            "  -w --download-dir <path>  Folder to set for new torrents\n"
72            "  -x --enable-pex           Enable peer exchange\n"
73            "  -X --disable-pex          Disable peer exchange\n" );
74    exit( 0 );
75}
76
77static int
78numarg( const char * arg )
79{
80    char * end = NULL;
81    const long num = strtol( arg, &end, 10 );
82    if( *end ) {
83        fprintf( stderr, "Not a number: \"%s\"\n", arg );
84        showUsage( );
85    }
86    return num;
87}
88
89static char * reqs[256]; /* arbitrary max */
90static int reqCount = 0;
91static int debug = 0;
92static char * auth = NULL;
93
94static char*
95absolutify( char * buf, size_t len, const char * path )
96{
97    if( *path == '/' )
98        tr_strlcpy( buf, path, len );
99    else {
100        char cwd[MAX_PATH_LENGTH];
101        getcwd( cwd, sizeof( cwd ) );
102        tr_buildPath( buf, len, cwd, path, NULL );
103    }
104    return buf;
105}
106
107static char*
108getEncodedMetainfo( const char * filename )
109{
110    size_t len = 0;
111    uint8_t * buf = tr_loadFile( filename, &len );
112    char * b64 = tr_base64_encode( buf, len, NULL );
113    tr_free( buf );
114    return b64;
115}
116
117static void
118readargs( int argc, char ** argv )
119{
120    int opt;
121    char optstr[] = "a:d:De:f:ghi:lmMp:r:s:S:t:u:Uv:w:xX";
122   
123    const struct option longopts[] =
124    {
125        { "add",                required_argument, NULL, 'a' },
126        { "download-limit",     required_argument, NULL, 'd' },
127        { "download-unlimited", no_argument,       NULL, 'D' },
128        { "encryption",         required_argument, NULL, 'e' },
129        { "files",              required_argument, NULL, 'f' },
130        { "debug",              no_argument,       NULL, 'g' },
131        { "help",               no_argument,       NULL, 'h' },
132        { "info",               required_argument, NULL, 'i' },
133        { "list",               no_argument,       NULL, 'l' },
134        { "port-mapping",       no_argument,       NULL, 'm' },
135        { "no-port-mapping",    no_argument,       NULL, 'M' },
136        { "port",               required_argument, NULL, 'p' },
137        { "remove",             required_argument, NULL, 'r' },
138        { "start",              required_argument, NULL, 's' },
139        { "stop",               required_argument, NULL, 'S' },
140        { "auth",               required_argument, NULL, 't' },
141        { "upload-limit",       required_argument, NULL, 'u' },
142        { "upload-unlimited",   no_argument,       NULL, 'U' },
143        { "verify",             required_argument, NULL, 'v' },
144        { "download-dir",       required_argument, NULL, 'w' },
145        { "enable-pex",         no_argument,       NULL, 'x' },
146        { "disable-pex",        no_argument,       NULL, 'X' },
147        { NULL, 0, NULL, 0 }
148    };
149
150    while((( opt = getopt_long( argc, argv, optstr, longopts, NULL ))) != -1 )
151    {
152        char * tmp;
153        char buf[MAX_PATH_LENGTH];
154        int addArg = TRUE;
155        int64_t fields = 0;
156        tr_benc top, *args;
157        tr_bencInitDict( &top, 3 );
158        args = tr_bencDictAddDict( &top, "arguments", 0 );
159
160        switch( opt )
161        {
162            case 'g': debug = 1;
163                      addArg = FALSE;
164                      break;
165            case 't': auth = tr_strdup( optarg );
166                      addArg = FALSE;
167                      break;
168            case 'h': showUsage( );
169                      addArg = FALSE;
170                      break;
171            case 'a': tr_bencDictAddStr( &top, "method", "torrent-add" );
172                      tr_bencDictAddStr( args, "metainfo", ((tmp=getEncodedMetainfo(optarg))) );
173                      tr_free( tmp );
174                      break;
175            case 'e': tr_bencDictAddStr( &top, "method", "session-set" );
176                      tr_bencDictAddStr( args, "encryption", optarg );
177                      break;
178            case 'f': tr_bencDictAddStr( &top, "method", "torrent-get" );
179                      tr_bencDictAddInt( &top, "tag", TAG_FILES );
180                      tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
181                      fields = TR_RPC_TORRENT_FIELD_ID
182                             | TR_RPC_TORRENT_FIELD_FILES
183                             | TR_RPC_TORRENT_FIELD_PRIORITIES;
184                      tr_bencDictAddInt( args, "fields", fields );
185                      break;
186            case 'i': tr_bencDictAddStr( &top, "method", "torrent-get" );
187                      tr_bencDictAddInt( &top, "tag", TAG_DETAILS );
188                      tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
189                      fields = TR_RPC_TORRENT_FIELD_ACTIVITY
190                             | TR_RPC_TORRENT_FIELD_ANNOUNCE
191                             | TR_RPC_TORRENT_FIELD_ERROR
192                             | TR_RPC_TORRENT_FIELD_HISTORY
193                             | TR_RPC_TORRENT_FIELD_ID
194                             | TR_RPC_TORRENT_FIELD_INFO
195                             | TR_RPC_TORRENT_FIELD_SCRAPE
196                             | TR_RPC_TORRENT_FIELD_SIZE
197                             | TR_RPC_TORRENT_FIELD_TRACKER_STATS;
198                      tr_bencDictAddInt( args, "fields", fields );
199                      break;
200            case 'd': tr_bencDictAddStr( &top, "method", "session-set" );
201                      tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
202                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
203                      break;
204            case 'D': tr_bencDictAddStr( &top, "method", "session-set" );
205                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
206                      break;
207            case 'u': tr_bencDictAddStr( &top, "method", "session-set" );
208                      tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
209                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
210                      break;
211            case 'U': tr_bencDictAddStr( &top, "method", "session-set" );
212                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
213                      break;
214            case 'l': tr_bencDictAddStr( &top, "method", "torrent-get" );
215                      tr_bencDictAddInt( &top, "tag", TAG_LIST );
216                      fields = TR_RPC_TORRENT_FIELD_ID
217                             | TR_RPC_TORRENT_FIELD_ACTIVITY
218                             | TR_RPC_TORRENT_FIELD_SIZE;
219                      tr_bencDictAddInt( args, "fields", fields );
220                      break;
221            case 'm': tr_bencDictAddStr( &top, "method", "session-set" );
222                      tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
223                      break;
224            case 'M': tr_bencDictAddStr( &top, "method", "session-set" );
225                      tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
226                      break;
227            case 'p': tr_bencDictAddStr( &top, "method", "session-set" );
228                      tr_bencDictAddInt( args, "port", numarg( optarg ) );
229                      break;
230            case 'r': tr_bencDictAddStr( &top, "method", "torrent-remove" );
231                      if( strcmp( optarg, "all" ) )
232                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
233                      break;
234            case 's': tr_bencDictAddStr( &top, "method", "torrent-start" );
235                      if( strcmp( optarg, "all" ) )
236                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
237                      break;
238            case 'S': tr_bencDictAddStr( &top, "method", "torrent-stop" );
239                      if( strcmp( optarg, "all" ) )
240                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
241                      break;
242            case 'v': tr_bencDictAddStr( &top, "method", "torrent-verify" );
243                      if( strcmp( optarg, "all" ) )
244                          tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), optarg, strlen(optarg) );
245                      break;
246            case 'w': tr_bencDictAddStr( &top, "method", "session-set" );
247                      tr_bencDictAddStr( args, "download-dir", absolutify(buf,sizeof(buf),optarg) );
248                      break;
249            case 'x': tr_bencDictAddStr( &top, "method", "session-set" );
250                      tr_bencDictAddInt( args, "pex-allowed", 1 );
251                      break;
252            case 'X': tr_bencDictAddStr( &top, "method", "session-set" );
253                      tr_bencDictAddInt( args, "pex-allowed", 0 );
254                      break;
255            default:
256                      showUsage( );
257                      addArg = FALSE;
258                      break;
259        }
260
261        if( addArg )
262            reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
263        tr_bencFree( &top );
264    }
265}
266
267/* [host:port] or [host] or [port] */
268static void
269getHostAndPort( int * argc, char ** argv, char ** host, int * port )
270{
271    if( *argv[1] != '-' )
272    {
273        int i;
274        const char * s = argv[1];
275        const char * delim = strchr( s, ':' );
276        if( delim ) { /* user passed in both host and port */
277            *host = tr_strndup( s, delim-s );
278            *port = atoi( delim+1 );
279        } else {
280            char * end;
281            const int i = strtol( s, &end, 10 );
282            if( !*end ) /* user passed in a port */
283                *port = i;
284            else /* user passed in a host */
285                *host = tr_strdup( s );
286        }
287
288        *argc -= 1;
289        for( i=1; i<*argc; ++i )
290            argv[i] = argv[i+1];
291    }
292}
293
294static size_t
295writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
296{
297    const size_t byteCount = size * nmemb;
298    evbuffer_add( buf, ptr, byteCount );
299    return byteCount;
300}
301
302static void
303etaToString( char * buf, size_t buflen, int64_t eta )
304{
305         if( eta < 0 )           snprintf( buf, buflen, "Unknown" );
306    else if( eta < 60 )          snprintf( buf, buflen, "%"PRId64"sec", eta );
307    else if( eta < (60*60) )     snprintf( buf, buflen, "%"PRId64" min", eta/60 );
308    else if( eta < (60*60*24) )  snprintf( buf, buflen, "%"PRId64" hrs", eta/(60*60) );
309    else                         snprintf( buf, buflen, "%"PRId64" days", eta/(60*60*24) );
310}
311
312#define KILOBYTE_FACTOR 1024.0
313#define MEGABYTE_FACTOR (1024.0 * 1024.0)
314#define GIGABYTE_FACTOR (1024.0 * 1024.0 * 1024.0)
315
316static char*
317strlratio( char * buf, double numerator, double denominator, size_t buflen )
318{
319    if( denominator )
320    {
321        const double ratio = numerator / denominator;
322        if( ratio < 10.0 )
323            snprintf( buf, buflen, "%'.2f", ratio );
324        else if( ratio < 100.0 )
325            snprintf( buf, buflen, "%'.1f", ratio );
326        else
327            snprintf( buf, buflen, "%'.0f", ratio );
328    }
329    else if( numerator )
330        tr_strlcpy( buf, "Infinity", buflen );
331    else
332        tr_strlcpy( buf, "None", buflen );
333    return buf;
334}
335
336static char*
337strlsize( char * buf, int64_t size, size_t buflen )
338{
339    if( !size )
340        tr_strlcpy( buf, "None", buflen );
341    else if( size < (int64_t)KILOBYTE_FACTOR )
342        snprintf( buf, buflen, "%'"PRId64" bytes", (int64_t)size );
343    else {
344        double displayed_size;
345        if (size < (int64_t)MEGABYTE_FACTOR) {
346            displayed_size = (double) size / KILOBYTE_FACTOR;
347            snprintf( buf, buflen, "%'.1f KB", displayed_size );
348        } else if (size < (int64_t)GIGABYTE_FACTOR) {
349            displayed_size = (double) size / MEGABYTE_FACTOR;
350            snprintf( buf, buflen, "%'.1f MB", displayed_size );
351        } else {
352            displayed_size = (double) size / GIGABYTE_FACTOR;
353            snprintf( buf, buflen, "%'.1f GB", displayed_size );
354        }
355    }
356    return buf;
357}
358
359static const char*
360torrentStatusToString( int i )
361{
362    switch( i )
363    {
364        case TR_STATUS_CHECK_WAIT: return "Will Verify";
365        case TR_STATUS_CHECK:      return "Verifying";
366        case TR_STATUS_DOWNLOAD:   return "Downloading";
367        case TR_STATUS_SEED:       return "Seeding";
368        case TR_STATUS_STOPPED:    return "Stopped";
369        default:                   return "Error";
370    }
371}
372
373static void
374printDetails( tr_benc * top )
375{
376    tr_benc *args, *torrents;
377
378    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
379        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
380    {
381        int ti, tCount;
382        for( ti=0, tCount=tr_bencListSize( torrents ); ti<tCount; ++ti )
383        {
384            tr_benc * t = tr_bencListChild( torrents, ti );
385            const char * str;
386            char buf[512];
387            char buf2[512];
388            int64_t i, j;
389
390            printf( "NAME\n" );
391            if( tr_bencDictFindInt( t, "id", &i ) )
392                printf( "  Id: %"PRId64"\n", i );
393            if( tr_bencDictFindStr( t, "name", &str ) )
394                printf( "  Name: %s\n", str );
395            if( tr_bencDictFindStr( t, "hashString", &str ) )
396                printf( "  Hash: %s\n", str );
397            printf( "\n" );
398
399            printf( "TRANSFER\n" );
400            if( tr_bencDictFindInt( t, "status", &i ) ) {
401                switch( i ) {
402                    case TR_STATUS_SEED:        str = "Seeding"; break;
403                    case TR_STATUS_DOWNLOAD:    str = "Downloading"; break;
404                    case TR_STATUS_STOPPED:     str = "Paused"; break;
405                    case TR_STATUS_CHECK:       str = "Verifying local data"; break;
406                    case TR_STATUS_CHECK_WAIT:  str = "Waiting to verify"; break;
407                    default:                    str = "error"; break;
408                }
409                printf( "  State: %s\n", str );
410            }
411            if( tr_bencDictFindInt( t, "eta", &i ) ) {
412                etaToString( buf, sizeof( buf ), i );
413                printf( "  ETA: %s\n", buf );
414            }
415            if( tr_bencDictFindInt( t, "rateDownload", &i ) )
416                printf( "  Download Speed: %.1f KB/s\n", i/1024.0 );
417            if( tr_bencDictFindInt( t, "rateUpload", &i ) )
418            {
419                printf( "  Upload Speed: %.1f KB/s\n", i/1024.0 );
420            }
421            if( tr_bencDictFindInt( t, "haveUnchecked", &i ) &&
422                tr_bencDictFindInt( t, "haveValid", &j ) )
423            {
424                strlsize( buf, i+j, sizeof( buf ) );
425                strlsize( buf2, j, sizeof( buf2 ) );
426                printf( "  Have: %s (%s verified)\n", buf, buf2 );
427            }
428            if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) &&
429                tr_bencDictFindInt( t, "leftUntilDone", &j ) )
430            {
431                strlratio( buf, (i-j), i, sizeof( buf ) );
432                printf( "  Progress: %s%%\n", buf );
433            }
434            if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) &&
435                tr_bencDictFindInt( t, "totalSize", &j ) )
436            {
437                strlsize( buf, j, sizeof( buf ) );
438                strlsize( buf2, i, sizeof( buf2 ) );
439                printf( "  Total size: %s (%s wanted)\n", buf, buf2 );
440            }
441            if( tr_bencDictFindInt( t, "downloadedEver", &i ) &&
442                tr_bencDictFindInt( t, "uploadedEver", &j ) ) {
443                strlsize( buf, i, sizeof( buf ) );
444                printf( "  Downloaded: %s\n", buf );
445                strlsize( buf, j, sizeof( buf ) );
446                printf( "  Uploaded: %s\n", buf );
447                strlratio( buf, i, j, sizeof( buf ) );
448                printf( "  Ratio: %s\n", buf );
449            }
450            if( tr_bencDictFindInt( t, "corruptEver", &i ) ) {
451                strlsize( buf, i, sizeof( buf ) );
452                printf( "  Corrupt DL: %s\n", buf );
453            }
454            if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str )
455                printf( "  Error: %s\n", str );
456            printf( "\n" );
457           
458            printf( "HISTORY\n" );
459            if( tr_bencDictFindInt( t, "addedDate", &i ) && i ) {
460                const time_t tt = i;
461                printf( "  Date added:      %s", ctime( &tt ) );
462            }
463            if( tr_bencDictFindInt( t, "doneDate", &i ) && i ) {
464                const time_t tt = i;
465                printf( "  Date finished:   %s", ctime( &tt ) );
466            }
467            if( tr_bencDictFindInt( t, "startDate", &i ) && i ) {
468                const time_t tt = i;
469                printf( "  Date started:    %s", ctime( &tt ) );
470            }
471            if( tr_bencDictFindInt( t, "activityDate", &i ) && i ) {
472                const time_t tt = i;
473                printf( "  Latest activity: %s", ctime( &tt ) );
474            }
475            printf( "\n" );
476           
477            printf( "TRACKER\n" );
478            if( tr_bencDictFindInt( t, "lastAnnounceTime", &i ) && i ) {
479                const time_t tt = i;
480                printf( "  Latest announce: %s", ctime( &tt ) );
481            }
482            if( tr_bencDictFindStr( t, "announceURL", &str ) )
483                printf( "  Announce URL: %s\n", str );
484            if( tr_bencDictFindStr( t, "announceResponse", &str ) && str && *str )
485                printf( "  Announce response: %s\n", str );
486            if( tr_bencDictFindInt( t, "nextAnnounceTime", &i ) && i ) {
487                const time_t tt = i;
488                printf( "  Next announce:   %s", ctime( &tt ) );
489            }
490            if( tr_bencDictFindInt( t, "lastScrapeTime", &i ) && i ) {
491                const time_t tt = i;
492                printf( "  Latest scrape:   %s", ctime( &tt ) );
493            }
494            if( tr_bencDictFindStr( t, "scrapeResponse", &str ) )
495                printf( "  Scrape response: %s\n", str );
496            if( tr_bencDictFindInt( t, "nextScrapeTime", &i ) && i ) {
497                const time_t tt = i;
498                printf( "  Next scrape:     %s", ctime( &tt ) );
499            }
500            if( tr_bencDictFindInt( t, "seeders", &i ) &&
501                tr_bencDictFindInt( t, "leechers", &j ) )
502                printf( "  Tracker knows of %"PRId64" seeders and %"PRId64" leechers\n", i, j );
503            if( tr_bencDictFindInt( t, "timesCompleted", &i ) )
504                printf( "  Tracker has seen %"PRId64" clients complete this torrent\n", i );
505            printf( "\n" );
506
507            printf( "ORIGINS\n" );
508            if( tr_bencDictFindInt( t, "dateCreated", &i ) && i ) {
509                const time_t tt = i;
510                printf( "  Date created: %s", ctime( &tt ) );
511            }
512            if( tr_bencDictFindInt( t, "isPrivate", &i ) )
513                printf( "  Public torrent: %s\n", ( i ? "No" : "Yes" ) );
514            if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
515                printf( "  Comment: %s\n", str );
516            if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
517                printf( "  Creator: %s\n", str );
518            if( tr_bencDictFindInt( t, "pieceCount", &i ) )
519                printf( "  Piece Count: %"PRId64"\n", i );
520            if( tr_bencDictFindInt( t, "pieceSize", &i ) )
521                printf( "  Piece Size: %"PRId64"\n", i );
522        }
523    }
524}
525
526static void
527printFileList( tr_benc * top )
528{
529    tr_benc *args, *torrents;
530
531    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
532        ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
533    {
534        int i, in;
535        for( i=0, in=tr_bencListSize( torrents ); i<in; ++i )
536        {
537            tr_benc * d = tr_bencListChild( torrents, i );
538            tr_benc *files, *priorities, *wanteds;
539            const char * name;
540            if( tr_bencDictFindStr( d, "name", &name ) &&
541                tr_bencDictFindList( d, "files", &files ) &&
542                tr_bencDictFindList( d, "priorities", &priorities ) &&
543                tr_bencDictFindList( d, "wanted", &wanteds ) )
544            {
545                int j=0, jn=tr_bencListSize(files);
546                printf( "%s (%d files):\n", name, jn );
547                printf("%3s  %8s %3s %9s  %s\n", "#", "Priority", "Get", "Size", "Name" );
548                for( j=0, jn=tr_bencListSize( files ); j<jn; ++j )
549                {
550                    int64_t length;
551                    int64_t priority;
552                    int64_t wanted;
553                    const char * filename;
554                    tr_benc * file = tr_bencListChild( files, j );
555                    if( tr_bencDictFindInt( file, "length", &length ) &&
556                        tr_bencDictFindStr( file, "name", &filename ) &&
557                        tr_bencGetInt( tr_bencListChild( priorities, j ), &priority ) &&
558                        tr_bencGetInt( tr_bencListChild( wanteds, j ), &wanted ) )
559                    {
560                        char sizestr[64];
561                        strlsize( sizestr, length, sizeof( sizestr ) );
562                        const char * pristr;
563                        switch( priority ) {
564                            case TR_PRI_LOW:    pristr = "Low"; break;
565                            case TR_PRI_HIGH:   pristr = "High"; break;
566                            default:            pristr = "Normal"; break;
567                        }
568                        printf( "%3d: %-8s %-3s %9s  %s\n", (j+1), pristr, (wanted?"Yes":"No"), sizestr, filename );
569                    }
570                }
571            }
572        }
573    }
574}
575
576static void
577printTorrentList( tr_benc * top )
578{
579    tr_benc *args, *list;
580
581    if( ( tr_bencDictFindDict( top, "arguments", &args ) ) &&
582        ( tr_bencDictFindList( args, "torrents", &list ) ) )
583    {
584        int i, n;
585        printf( "%-3s  %-4s  %-8s  %-5s  %-5s  %-5s  %-11s  %s\n",
586                "ID", "Done", "ETA", "Up", "Down", "Ratio", "Status", "Name" );
587        for( i=0, n=tr_bencListSize( list ); i<n; ++i )
588        {
589            int64_t id, eta, status, up, down, sizeWhenDone, leftUntilDone;
590            const char *name;
591            tr_benc * d = tr_bencListChild( list, i );
592            if(    tr_bencDictFindInt( d, "eta", &eta )
593                && tr_bencDictFindInt( d, "id", &id )
594                && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
595                && tr_bencDictFindStr( d, "name", &name )
596                && tr_bencDictFindInt( d, "rateDownload", &down )
597                && tr_bencDictFindInt( d, "rateUpload", &up )
598                && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
599                && tr_bencDictFindInt( d, "status", &status ) )
600            {
601                char etaStr[16];
602                if( leftUntilDone )
603                    etaToString( etaStr, sizeof( etaStr ), eta );
604                else
605                    snprintf( etaStr, sizeof( etaStr ), "Done" );
606                printf( "%3d  %3d%%  %-8s  %5.1f  %5.1f  %5.1f  %-11s  %s\n",
607                        (int)id,
608                        (int)(100.0*(sizeWhenDone-leftUntilDone)/sizeWhenDone),
609                        etaStr,
610                        up / 1024.0,
611                        down / 1024.0,
612                        (double)(sizeWhenDone-leftUntilDone)/sizeWhenDone,
613                        torrentStatusToString( status ),
614                        name );
615            }
616        }
617    }
618}
619
620static void
621processResponse( const char * host, int port,
622                 const void * response, size_t len )
623{
624    tr_benc top;
625
626    if( debug )
627        fprintf( stderr, "got response: [%*.*s]\n",
628                 (int)len, (int)len, (const char*) response );
629
630    if( tr_jsonParse( response, len, &top, NULL ) )
631       tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char*)response );
632    else
633    {
634        int64_t tag = -1;
635        const char * str;
636        tr_bencDictFindInt( &top, "tag", &tag );
637
638        if( tr_bencDictFindStr( &top, "result", &str ) )
639            printf( "%s:%d responded: \"%s\"\n", host, port, str );
640        switch( tag ) {
641            case TAG_FILES: printFileList( &top ); break;
642            case TAG_DETAILS: printDetails( &top ); break;
643            case TAG_LIST: printTorrentList( &top ); break;
644            default: break;
645        }
646
647        tr_bencFree( &top );
648    }
649}
650
651static void
652processRequests( const char * host, int port,
653                 const char ** reqs, int reqCount )
654{
655    int i;
656    CURL * curl;
657    struct evbuffer * buf = evbuffer_new( );
658    char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
659
660    curl = curl_easy_init( );
661    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
662    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME"/"LONG_VERSION_STRING );
663    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
664    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
665    curl_easy_setopt( curl, CURLOPT_POST, 1 );
666    curl_easy_setopt( curl, CURLOPT_URL, url );
667    if( auth ) {
668        curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
669        curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
670    }
671
672    for( i=0; i<reqCount; ++i )
673    {
674        CURLcode res;
675        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
676        if( debug )
677            tr_ninf( MY_NAME, "posting [%s]\n", reqs[i] );
678        if(( res = curl_easy_perform( curl )))
679            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
680        else
681            processResponse( host, port, EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
682
683        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
684    }
685
686    /* cleanup */
687    tr_free( url );
688    evbuffer_free( buf );
689    curl_easy_cleanup( curl );
690}
691
692int
693main( int argc, char ** argv )
694{
695    int i;
696    int port = DEFAULT_PORT;
697    char * host = NULL;
698
699    if( argc < 2 )
700        showUsage( );
701
702    getHostAndPort( &argc, argv, &host, &port );
703    if( host == NULL )
704        host = tr_strdup( DEFAULT_HOST );
705
706    readargs( argc, argv );
707    if( reqCount )
708        processRequests( host, port, (const char**)reqs, reqCount );
709    else
710        showUsage( );
711
712    for( i=0; i<reqCount; ++i )
713        tr_free( reqs[i] );
714
715    tr_free( host );
716    return 0;
717}
Note: See TracBrowser for help on using the repository browser.