source: trunk/daemon/remote.c @ 5857

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

tweak the daemon & remote console messages a little

  • Property svn:keywords set to Date Rev Author Id
File size: 13.5 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 5857 2008-05-20 14:01: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 };
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            "  -c --encryption required  Require encryption for all peers\n"
49            "  -c --encryption preferred Prefer peers to use encryption\n"
50            "  -c --encryption tolerated Prefer peers to use plaintext\n"
51            "  -d --download-limit <int> Max download rate in KiB/s\n"
52            "  -D --download-unlimited   No download rate limit\n"
53            "  -e --enable-pex           Enable peer exchange\n"
54            "  -E --disable-pex          Disable peer exchange\n"
55            "  -f --folder <path>        Folder to set for new torrents\n"
56            "  -g --debug                Print debugging information\n"
57            "  -h --help                 Display this message and exit\n"
58            "  -l --list                 Long list of all torrent and status\n"
59            "  -m --port-mapping         Automatic port mapping via NAT-PMP or UPnP\n"
60            "  -M --no-port-mapping      Disable automatic port mapping\n"
61            "  -p --port <int>           Port to listen for incoming peers\n"
62            "  -r --remove <int>         Remove the torrent with the given ID\n"
63            "  -r --remove all           Remove all torrents\n"
64            "  -s --start <int>          Start the torrent with the given ID\n"
65            "  -s --start all            Start all stopped torrents\n"
66            "  -S --stop <int>           Stop the torrent with the given ID\n"
67            "  -S --stop all             Stop all running torrents\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    exit( 0 );
72}
73
74static int
75numarg( const char * arg )
76{
77    char * end = NULL;
78    const long num = strtol( arg, &end, 10 );
79    if( *end ) {
80        fprintf( stderr, "Not a number: \"%s\"\n", arg );
81        showUsage( );
82    }
83    return num;
84}
85
86static char * reqs[256]; /* arbitrary max */
87static int reqCount = 0;
88static int debug = 0;
89
90static char*
91absolutify( char * buf, size_t len, const char * path )
92{
93    if( *path == '/' )
94        tr_strlcpy( buf, path, len );
95    else {
96        char cwd[MAX_PATH_LENGTH];
97        getcwd( cwd, sizeof( cwd ) );
98        tr_buildPath( buf, len, cwd, path, NULL );
99    }
100    return buf;
101}
102
103static void
104readargs( int argc, char ** argv )
105{
106    int opt;
107    char optstr[] = "a:c:d:DeEf:ghlmMp:r:s:S:u:Uv:";
108   
109    const struct option longopts[] =
110    {
111        { "add",                required_argument, NULL, 'a' },
112        { "encryption",         required_argument, NULL, 'c' },
113        { "download-limit",     required_argument, NULL, 'd' },
114        { "download-unlimited", no_argument,       NULL, 'D' },
115        { "enable-pex",         no_argument,       NULL, 'e' },
116        { "disable-pex",        no_argument,       NULL, 'E' },
117        { "folder",             required_argument, NULL, 'f' },
118        { "debug",              no_argument,       NULL, 'g' },
119        { "help",               no_argument,       NULL, 'h' },
120        { "list",               no_argument,       NULL, 'l' },
121        { "port-mapping",       no_argument,       NULL, 'm' },
122        { "no-port-mapping",    no_argument,       NULL, 'M' },
123        { "port",               required_argument, NULL, 'p' },
124        { "remove",             required_argument, NULL, 'r' },
125        { "start",              required_argument, NULL, 's' },
126        { "stop",               required_argument, NULL, 'S' },
127        { "upload-limit",       required_argument, NULL, 'u' },
128        { "upload-unlimited",   no_argument,       NULL, 'U' },
129        { "verify",             required_argument, NULL, 'v' },
130        { NULL, 0, NULL, 0 }
131    };
132
133    while((( opt = getopt_long( argc, argv, optstr, longopts, NULL ))) != -1 )
134    {
135        char buf[MAX_PATH_LENGTH];
136        tr_benc top, *args;
137        tr_bencInitDict( &top, 3 );
138        args = tr_bencDictAddDict( &top, "args", 0 );
139
140        switch( opt )
141        {
142            case 'g': debug = 1; break;
143            case 'h': showUsage( ); break;
144            case 'a': tr_bencDictAddStr( &top, "method", "torrent-add" );
145                      tr_bencDictAddStr( args, "filename", optarg );
146                      break;
147            case 'c': tr_bencDictAddStr( &top, "method", "session-set" );
148                      tr_bencDictAddStr( args, "encryption", optarg );
149                      break;
150            case 'd': tr_bencDictAddStr( &top, "method", "session-set" );
151                      tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) );
152                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 );
153                      break;
154            case 'D': tr_bencDictAddStr( &top, "method", "session-set" );
155                      tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 );
156                      break;
157            case 'u': tr_bencDictAddStr( &top, "method", "session-set" );
158                      tr_bencDictAddInt( args, "speed-limit-up", numarg( optarg ) );
159                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 1 );
160                      break;
161            case 'U': tr_bencDictAddStr( &top, "method", "session-set" );
162                      tr_bencDictAddInt( args, "speed-limit-up-enabled", 0 );
163                      break;
164            case 'e': tr_bencDictAddStr( &top, "method", "session-set" );
165                      tr_bencDictAddInt( args, "pex-allowed", 1 );
166                      break;
167            case 'E': tr_bencDictAddStr( &top, "method", "session-set" );
168                      tr_bencDictAddInt( args, "pex-allowed", 0 );
169                      break;
170            case 'f': tr_bencDictAddStr( &top, "method", "session-set" );
171                      tr_bencDictAddStr( args, "download-dir", absolutify(buf,sizeof(buf),optarg) );
172                      break;
173            case 'l': tr_bencDictAddStr( &top, "method", "torrent-list" );
174                      tr_bencDictAddInt( &top, "tag", TAG_LIST );
175                      break;
176            case 'm': tr_bencDictAddStr( &top, "method", "session-set" );
177                      tr_bencDictAddInt( args, "port-forwarding-enabled", 1 );
178                      break;
179            case 'M': tr_bencDictAddStr( &top, "method", "session-set" );
180                      tr_bencDictAddInt( args, "port-forwarding-enabled", 0 );
181                      break;
182            case 'p': tr_bencDictAddStr( &top, "method", "session-set" );
183                      tr_bencDictAddInt( args, "port", numarg( optarg ) );
184                      break;
185            case 'r': tr_bencDictAddStr( &top, "method", "torrent-remove" );
186                      if( strcmp( optarg, "all" ) )
187                          tr_bencDictAddStr( args, "ids", optarg );
188                      break;
189            case 's': tr_bencDictAddStr( &top, "method", "torrent-start" );
190                      if( strcmp( optarg, "all" ) )
191                          tr_bencDictAddStr( args, "ids", optarg );
192                      break;
193            case 'S': tr_bencDictAddStr( &top, "method", "torrent-stop" );
194                      if( strcmp( optarg, "all" ) )
195                          tr_bencDictAddStr( args, "ids", optarg );
196                      break;
197            case 'v': tr_bencDictAddStr( &top, "method", "torrent-verify" );
198                      if( strcmp( optarg, "all" ) )
199                          tr_bencDictAddStr( args, "ids", optarg );
200                      break;
201            default:
202                      showUsage( );
203                      break;
204        }
205
206        reqs[reqCount++] = tr_bencSaveAsJSON( &top, NULL );
207        tr_bencFree( &top );
208    }
209}
210
211/* [host:port] or [host] or [port] */
212static void
213getHostAndPort( int * argc, char ** argv, char ** host, int * port )
214{
215    if( *argv[1] != '-' )
216    {
217        int i;
218        const char * s = argv[1];
219        const char * delim = strchr( s, ':' );
220        if( delim ) { /* user passed in both host and port */
221            *host = tr_strndup( s, delim-s );
222            *port = atoi( delim+1 );
223        } else {
224            char * end;
225            const int i = strtol( s, &end, 10 );
226            if( !*end ) /* user passed in a port */
227                *port = i;
228            else /* user passed in a host */
229                *host = tr_strdup( s );
230        }
231
232        *argc -= 1;
233        for( i=1; i<*argc; ++i )
234            argv[i] = argv[i+1];
235    }
236}
237
238static size_t
239writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
240{
241    const size_t byteCount = size * nmemb;
242    evbuffer_add( buf, ptr, byteCount );
243    return byteCount;
244}
245
246static const char*
247torrentStatusToString( int i )
248{
249    switch( i )
250    {
251        case TR_STATUS_CHECK_WAIT: return "Will Verify";
252        case TR_STATUS_CHECK:      return "Verifying";
253        case TR_STATUS_DOWNLOAD:   return "Downloading";
254        case TR_STATUS_SEED:       return "Seeding";
255        case TR_STATUS_STOPPED:    return "Stopped";
256        default:                   return "Error";
257    }
258}
259
260static void
261processResponse( const char * host, int port,
262                 const void * response, size_t len )
263{
264    tr_benc top;
265
266    if( tr_jsonParse( response, len, &top, NULL ) )
267       tr_nerr( MY_NAME, "Unable to parse response" );
268    else
269    {
270        tr_benc *args, *list;
271        int64_t tag = -1;
272        const char * str;
273        tr_bencDictFindInt( &top, "tag", &tag );
274
275        if( tr_bencDictFindStr( &top, "result", &str ) )
276            printf( "%s:%d responded: \"%s\"\n", host, port, str );
277
278        if( ( tag == TAG_LIST ) &&
279            ( tr_bencDictFindDict( &top, "arguments", &args ) ) &&
280            ( tr_bencDictFindList( args, "list", &list ) ) )
281        {
282            int i, n;
283            for( i=0, n=tr_bencListSize( list ); i<n; ++i )
284            {
285                int64_t id, status;
286                const char *name, *ratiostr, *upstr, *dnstr;
287                tr_benc * d = tr_bencListChild( list, i );
288                if(    tr_bencDictFindInt( d, "id", &id )
289                    && tr_bencDictFindInt( d, "status", &status )
290                    && tr_bencDictFindStr( d, "name", &name )
291                    && tr_bencDictFindStr( d, "ratio", &ratiostr )
292                    && tr_bencDictFindStr( d, "rateUpload", &upstr )
293                    && tr_bencDictFindStr( d, "rateDownload", &dnstr ) )
294                {
295                    printf( "%4d.  Up: %5.1f  Down: %5.1f  Ratio: %4.1f  %-15s  %s\n",
296                            (int)id,
297                            strtod( upstr, NULL ),
298                            strtod( dnstr, NULL ),
299                            strtod( ratiostr, NULL ),
300                            torrentStatusToString( status ),
301                            name );
302                }
303            }
304        }
305
306        tr_bencFree( &top );
307    }
308}
309
310static void
311processRequests( const char * host, int port,
312                 const char ** reqs, int reqCount )
313{
314    int i;
315    CURL * curl;
316    struct evbuffer * buf = evbuffer_new( );
317    char * url = tr_strdup_printf( "http://%s:%d/transmission", host, port );
318
319    curl = curl_easy_init( );
320    curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
321    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME"/"LONG_VERSION_STRING );
322    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
323    curl_easy_setopt( curl, CURLOPT_WRITEDATA, buf );
324    curl_easy_setopt( curl, CURLOPT_POST, 1 );
325    curl_easy_setopt( curl, CURLOPT_URL, url );
326
327    for( i=0; i<reqCount; ++i )
328    {
329        CURLcode res;
330        curl_easy_setopt( curl, CURLOPT_POSTFIELDS, reqs[i] );
331        if(( res = curl_easy_perform( curl )))
332            tr_nerr( MY_NAME, "(%s:%d) %s", host, port, curl_easy_strerror( res ) );
333        else
334            processResponse( host, port, EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
335
336        evbuffer_drain( buf, EVBUFFER_LENGTH( buf ) );
337    }
338
339    /* cleanup */
340    tr_free( url );
341    evbuffer_free( buf );
342    curl_easy_cleanup( curl );
343}
344
345int
346main( int argc, char ** argv )
347{
348    int i;
349    int port = DEFAULT_PORT;
350    char * host = NULL;
351
352    if( argc < 2 )
353        showUsage( );
354
355    getHostAndPort( &argc, argv, &host, &port );
356    if( host == NULL )
357        host = tr_strdup( DEFAULT_HOST );
358
359    readargs( argc, argv );
360    if( reqCount )
361        processRequests( host, port, (const char**)reqs, reqCount );
362    else
363        showUsage( );
364
365    for( i=0; i<reqCount; ++i )
366        tr_free( reqs[i] );
367
368    tr_free( host );
369    return 0;
370}
Note: See TracBrowser for help on using the repository browser.