source: trunk/daemon/remote.c @ 6316

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

(daemon) let file download flags and priorities be set by transmission-remote. Add examples to the man page and --help.
(libT) minor tweaks to tr-getopt
(gtk) tweak the preference dialog's "port forwarding" text for clarity as suggested in the forums

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