source: trunk/cli/transmissioncli.c @ 6298

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

have daemon and cli use tr-getopt too.

  • Property svn:keywords set to Date Rev Author Id
File size: 17.3 KB
Line 
1/******************************************************************************
2 * $Id: transmissioncli.c 6298 2008-07-08 16:50:34Z charles $
3 *
4 * Copyright (c) 2005-2006 Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <unistd.h>
29#include <signal.h>
30
31#include <libtransmission/transmission.h>
32#include <libtransmission/bencode.h>
33#include <libtransmission/makemeta.h>
34#include <libtransmission/metainfo.h> /* tr_metainfoFree */
35#include <libtransmission/tr-getopt.h>
36#include <libtransmission/utils.h> /* tr_wait */
37#include <libtransmission/web.h> /* tr_webRun */
38
39#define MY_NAME "transmission-cli"
40
41static int           showInfo      = 0;
42static int           showScrape    = 0;
43static int           isPrivate     = 0;
44static int           verboseLevel  = 0;
45static int           peerPort      = TR_DEFAULT_PORT;
46static int           peerSocketTOS = TR_DEFAULT_PEER_SOCKET_TOS;
47static int           uploadLimit   = 20;
48static int           downloadLimit = -1;
49static int           natTraversal  = 0;
50static int           verify        = 0;
51static sig_atomic_t  gotsig        = 0;
52static sig_atomic_t  manualUpdate  = 0;
53
54static const char   * torrentPath   = NULL;
55static const char   * downloadDir   = NULL;
56static const char   * finishCall   = NULL;
57static const char   * announce     = NULL;
58static const char   * configdir    = NULL;
59static const char   * sourceFile   = NULL;
60static const char   * comment      = NULL;
61
62static int  parseCommandLine ( int argc, const char ** argv );
63static void sigHandler       ( int signal );
64
65static char *
66getStringRatio( float ratio )
67{
68    static char string[20];
69
70    if( ratio == TR_RATIO_NA )
71        return "n/a";
72    snprintf( string, sizeof string, "%.3f", ratio );
73    return string;
74}
75
76static int
77is_rfc2396_alnum( char ch )
78{
79    return     ( '0' <= ch && ch <= '9' )
80            || ( 'A' <= ch && ch <= 'Z' )
81            || ( 'a' <= ch && ch <= 'z' );
82}
83
84static void
85escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
86{
87    const uint8_t *end = in + in_len;
88    while( in != end )
89        if( is_rfc2396_alnum(*in) )
90            *out++ = (char) *in++;
91        else
92            out += snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
93    *out = '\0';
94}
95
96
97#define LINEWIDTH 80
98
99static void
100torrentStateChanged( tr_torrent   * torrent UNUSED,
101                     cp_status_t    status UNUSED,
102                     void         * user_data UNUSED )
103{
104    system( finishCall );
105}
106
107static int leftToScrape = 0;
108
109static void
110scrapeDoneFunc( struct tr_handle    * session UNUSED,
111                long                  response_code,
112                const void          * response,
113                size_t                response_byte_count,
114                void                * host )
115{
116    tr_benc top, *files;
117
118    if( !tr_bencLoad( response, response_byte_count, &top, NULL ) 
119        && tr_bencDictFindDict( &top, "files", &files )
120        && files->val.l.count >= 2 )
121    {
122        int64_t complete=-1, incomplete=-1, downloaded=-1;
123        tr_benc * hash = &files->val.l.vals[1];
124        tr_bencDictFindInt( hash, "complete", &complete );
125        tr_bencDictFindInt( hash, "incomplete", &incomplete );
126        tr_bencDictFindInt( hash, "downloaded", &downloaded );
127        printf( "%4d seeders, %4d leechers, %5d downloads at %s\n",
128                (int)complete, (int)incomplete, (int)downloaded, (char*)host );
129        tr_bencFree( &top );
130    }
131    else
132        printf( "unable to parse response (http code %lu) at %s", response_code, (char*)host );
133
134    --leftToScrape;
135}
136
137int
138main( int argc, char ** argv )
139{
140    int i, error;
141    tr_handle  * h;
142    tr_ctor * ctor;
143    tr_torrent * tor = NULL;
144    char cwd[MAX_PATH_LENGTH];
145
146    printf( "Transmission %s - http://www.transmissionbt.com/\n",
147            LONG_VERSION_STRING );
148
149    /* Get options */
150    if( parseCommandLine( argc, (const char**)argv ) )
151        return EXIT_FAILURE;
152
153    /* Check the options for validity */
154    if( !torrentPath ) {
155        printf( "No torrent specified!\n" );
156        return EXIT_FAILURE;
157    }
158    if( peerPort < 1 || peerPort > 65535 ) {
159        printf( "Invalid port '%d'\n", peerPort );
160        return EXIT_FAILURE;
161    }
162    if( peerSocketTOS < 0 || peerSocketTOS > 255 ) {
163        printf( "Invalid TOS '%d'\n", peerSocketTOS );
164        return EXIT_FAILURE;
165    }
166
167    /* don't bind the port if we're just running the CLI
168     * to get metainfo or to create a torrent */
169    if( showInfo || showScrape || ( sourceFile != NULL ) )
170        peerPort = -1;
171
172    if( configdir == NULL )
173        configdir = tr_getDefaultConfigDir( );
174
175    /* if no download directory specified, use cwd instead */
176    if( !downloadDir ) {
177        getcwd( cwd, sizeof( cwd ) );
178        downloadDir = cwd;
179    }
180
181
182    /* Initialize libtransmission */
183    h = tr_sessionInitFull(
184            configdir,
185            "cli",                         /* tag */
186            downloadDir,                   /* where to download torrents */
187            TR_DEFAULT_PEX_ENABLED,
188            natTraversal,                  /* nat enabled */
189            peerPort,
190            TR_ENCRYPTION_PREFERRED,
191            uploadLimit >= 0,
192            uploadLimit,
193            downloadLimit >= 0,
194            downloadLimit,
195            TR_DEFAULT_GLOBAL_PEER_LIMIT,
196            verboseLevel + 1,              /* messageLevel */
197            0,                             /* is message queueing enabled? */
198            TR_DEFAULT_BLOCKLIST_ENABLED,
199            peerSocketTOS,
200            TR_DEFAULT_RPC_ENABLED,
201            TR_DEFAULT_RPC_PORT,
202            TR_DEFAULT_RPC_ACL,
203            FALSE, "fnord", "potzrebie",
204            TR_DEFAULT_PROXY_ENABLED,
205            TR_DEFAULT_PROXY,
206            TR_DEFAULT_PROXY_TYPE,
207            TR_DEFAULT_PROXY_AUTH_ENABLED,
208            TR_DEFAULT_PROXY_USERNAME,
209            TR_DEFAULT_PROXY_PASSWORD );
210
211    if( sourceFile && *sourceFile ) /* creating a torrent */
212    {
213        int err;
214        tr_metainfo_builder * builder = tr_metaInfoBuilderCreate( h, sourceFile );
215        tr_tracker_info ti;
216        ti.tier = 0;
217        ti.announce = (char*) announce;
218        tr_makeMetaInfo( builder, torrentPath, &ti, 1, comment, isPrivate );
219        while( !builder->isDone ) {
220            tr_wait( 1000 );
221            printf( "." );
222        }
223        err = builder->result;
224        tr_metaInfoBuilderFree( builder );
225        return err;
226    }
227
228    ctor = tr_ctorNew( h );
229    tr_ctorSetMetainfoFromFile( ctor, torrentPath );
230    tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
231    tr_ctorSetDownloadDir( ctor, TR_FORCE, downloadDir );
232
233    if( showScrape )
234    {
235        tr_info info;
236
237        if( !tr_torrentParse( h, ctor, &info ) )
238        {
239            int i;
240            const time_t start = time( NULL );
241            for( i=0; i<info.trackerCount; ++i )
242            {
243                if( info.trackers[i].scrape )
244                {
245                    const char * scrape = info.trackers[i].scrape;
246                    char escaped[SHA_DIGEST_LENGTH*3 + 1];
247                    char *url, *host;
248                    escape( escaped, info.hash, SHA_DIGEST_LENGTH );
249                    url = tr_strdup_printf( "%s%cinfo_hash=%s",
250                                            scrape,
251                                            strchr(scrape,'?')?'&':'?',
252                                            escaped );
253                    tr_httpParseURL( scrape, -1, &host, NULL, NULL );
254                    ++leftToScrape;
255                    tr_webRun( h, url, NULL, scrapeDoneFunc, host );
256                    tr_free( url );
257                }
258            }
259
260            fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
261
262            while( leftToScrape>0 && ((time(NULL)-start)<20) )
263                tr_wait( 250 );
264        }
265        goto cleanup;
266    }
267
268    if( showInfo )
269    {
270        tr_info info;
271
272        if( !tr_torrentParse( h, ctor, &info ) )
273        {
274            int prevTier = -1;
275            tr_file_index_t ff;
276
277            printf( "hash:\t" );
278            for( i=0; i<SHA_DIGEST_LENGTH; ++i )
279                printf( "%02x", info.hash[i] );
280            printf( "\n" );
281
282            printf( "name:\t%s\n", info.name );
283
284            for( i=0; i<info.trackerCount; ++i ) {
285                if( prevTier != info.trackers[i].tier ) {
286                    prevTier = info.trackers[i].tier;
287                    printf( "\ntracker tier #%d:\n", (prevTier+1) );
288                }
289                printf( "\tannounce:\t%s\n", info.trackers[i].announce );
290            }
291
292            printf( "size:\t%"PRIu64" (%"PRIu64" * %d + %"PRIu64")\n",
293                    info.totalSize, info.totalSize / info.pieceSize,
294                    info.pieceSize, info.totalSize % info.pieceSize );
295
296            if( info.comment[0] )
297                printf( "comment:\t%s\n", info.comment );
298            if( info.creator[0] )
299                printf( "creator:\t%s\n", info.creator );
300            if( info.isPrivate )
301                printf( "private flag set\n" );
302
303            printf( "file(s):\n" );
304            for( ff=0; ff<info.fileCount; ++ff )
305                printf( "\t%s (%"PRIu64")\n", info.files[ff].name, info.files[ff].length );
306
307            tr_metainfoFree( &info );
308        }
309
310        tr_ctorFree( ctor );
311        goto cleanup;
312    }
313
314    tor = tr_torrentNew( h, ctor, &error );
315    tr_ctorFree( ctor );
316    if( tor == NULL )
317    {
318        printf( "Failed opening torrent file `%s'\n", torrentPath );
319        tr_sessionClose( h );
320        return EXIT_FAILURE;
321    }
322
323    signal( SIGINT, sigHandler );
324    signal( SIGHUP, sigHandler );
325
326    tr_torrentSetStatusCallback( tor, torrentStateChanged, NULL );
327    tr_torrentStart( tor );
328
329    for( ;; )
330    {
331        char string[LINEWIDTH];
332        int  chars = 0;
333        const struct tr_stat * s;
334
335        tr_wait( 1000 );
336
337        if( gotsig )
338        {
339            gotsig = 0;
340            tr_torrentStop( tor );
341            tr_sessionSetPortForwardingEnabled( h, 0 );
342        }
343       
344        if( manualUpdate )
345        {
346            manualUpdate = 0;
347            if ( !tr_torrentCanManualUpdate( tor ) )
348                fprintf( stderr, "\rReceived SIGHUP, but can't send a manual update now\n" );
349            else {
350                fprintf( stderr, "\rReceived SIGHUP: manual update scheduled\n" );
351                tr_torrentManualUpdate( tor );
352            }
353        }
354       
355        if( verify )
356        {
357            verify = 0;
358            tr_torrentVerify( tor );
359        }
360
361        s = tr_torrentStat( tor );
362
363        if( s->status & TR_STATUS_CHECK_WAIT )
364        {
365            chars = snprintf( string, sizeof string,
366                "Waiting to verify local files..." );
367        }
368        else if( s->status & TR_STATUS_CHECK )
369        {
370            chars = snprintf( string, sizeof string,
371                "Verifying local files... %.2f%%, found %.2f%% valid", 100 * s->recheckProgress, 100.0 * s->percentDone );
372        }
373        else if( s->status & TR_STATUS_DOWNLOAD )
374        {
375            chars = snprintf( string, sizeof string,
376                "Progress: %.2f %%, %d peer%s, dl from %d (%.2f KB/s), "
377                "ul to %d (%.2f KB/s) [%s]", 100.0 * s->percentDone,
378                s->peersConnected, ( s->peersConnected == 1 ) ? "" : "s",
379                s->peersSendingToUs, s->rateDownload,
380                s->peersGettingFromUs, s->rateUpload,
381                getStringRatio(s->ratio) );
382        }
383        else if( s->status & TR_STATUS_SEED )
384        {
385            chars = snprintf( string, sizeof string,
386                "Seeding, uploading to %d of %d peer(s), %.2f KB/s [%s]",
387                s->peersGettingFromUs, s->peersConnected,
388                s->rateUpload, getStringRatio(s->ratio) );
389        }
390        else if( s->status & TR_STATUS_STOPPED )
391        {
392            break;
393        }
394        if( ( signed )sizeof string > chars )
395        {
396            memset( &string[chars], ' ', sizeof string - 1 - chars );
397        }
398        string[sizeof string - 1] = '\0';
399        fprintf( stderr, "\r%s", string );
400
401        if( s->error )
402        {
403            fprintf( stderr, "\n%s\n", s->errorString );
404        }
405        else if( verboseLevel > 0 )
406        {
407            fprintf( stderr, "\n" );
408        }
409    }
410    fprintf( stderr, "\n" );
411
412    /* try for 5 seconds to delete any port mappings for nat traversal */
413    tr_sessionSetPortForwardingEnabled( h, 0 );
414    for( i=0; i<10; ++i ) {
415        const tr_port_forwarding f = tr_sessionGetPortForwarding( h );
416        if( f == TR_PORT_UNMAPPED )
417            break;
418        tr_wait( 500 );
419    }
420   
421cleanup:
422    tr_sessionClose( h );
423
424    return EXIT_SUCCESS;
425}
426
427/***
428****
429****
430****
431***/
432
433static const char *
434getUsage( void )
435{
436    return "A fast and easy BitTorrent client\n"
437           "\n"
438           "Usage: "MY_NAME" [options] <torrent-filename>";
439}
440
441const struct tr_option options[] = {
442    { 'a', "announce",     "When creating a new torrent, set its announce URL",   "a", 1, "<url>" },
443    { 'c', "comment",      "When creating a new torrent, set its comment field",  "c", 1, "<comment>" },
444    { 'd', "downlimit",    "Set the maxiumum download speed in KB/s",             "d", 1, "<number>" },
445    { 'D', "no-downlimit", "Don't limit the download speed",                      "D", 0, NULL },
446    { 'f', "finish",       "Set a script to run when the torrent finishes",       "f", 1, "<script>" },
447    { 'g', "config-dir",   "Where to look for configuration files",               "g", 1, "<path>" },
448    { 'i', "info",         "Show torrent details and exit",                       "i", 0, NULL },
449    { 'm', "portmap",      "Enable portmapping via NAT-PMP or UPnP",              "m", 0, NULL },
450    { 'M', "no-portmap",   "Disable portmapping",                                 "M", 0, NULL },
451    { 'n', "new",          "Create a new torrent from a file or directory",       "n", 1, "<path>" },
452    { 'p', "port",         "Port to listen for incoming peers (Default: "TR_DEFAULT_RPC_PORT_STR")", "p", 1, "<port>" },
453    { 'r', "private",      "When creating a new torrent, set its 'private' flag", "r", 0, NULL },
454    { 's', "scrape",       "Scrape the torrent and exit",                         "s", 0, NULL },
455    { 't', "tos",          "Peer socket TOS (0 to 255, default="TR_DEFAULT_PEER_SOCKET_TOS_STR")", "t", 1, "<number>"},
456    { 'u', "uplimit",      "Set the maxiumum upload speed in KB/s",               "u", 1, "<number>" },
457    { 'U', "no-uplimit",   "Don't limit the upload speed",                        "U", 0, NULL },
458    { 'v', "verify",       "Verify the specified torrent",                        "v", 0, NULL },
459    { 'w', "download-dir", "Where to save downloaded data",                       "w", 1, "<path>" },
460    { 0, NULL, NULL, NULL, 0, NULL }
461};
462
463static void
464showUsage( void )
465{
466    tr_getopt_usage( MY_NAME, getUsage(), options );
467    exit( 0 );
468}
469
470static int
471numarg( const char * arg )
472{
473    char * end = NULL;
474    const long num = strtol( arg, &end, 10 );
475    if( *end ) {
476        fprintf( stderr, "Not a number: \"%s\"\n", arg );
477        showUsage( );
478    }
479    return num;
480}
481
482static int
483parseCommandLine( int argc, const char ** argv )
484{
485    int c;
486    const char * optarg;
487
488    while(( c = tr_getopt( getUsage(), argc, argv, options, &optarg )))
489    {
490        switch( c )
491        {
492            case 'a': announce = optarg; break;
493            case 'c': comment = optarg; break;
494            case 'd': downloadLimit = numarg( optarg ); break;
495            case 'D': downloadLimit = -1; break;
496            case 'f': finishCall = optarg; break;
497            case 'g': configdir = optarg; break;
498            case 'i': showInfo = 1; break;
499            case 'm': natTraversal = 1; break;
500            case 'M': natTraversal = 0; break;
501            case 'n': sourceFile = optarg; break;
502            case 'p': peerPort = numarg( optarg ); break;
503            case 'r': isPrivate = 1; break;
504            case 's': showScrape = 1; break;
505            case 't': peerSocketTOS = numarg( optarg ); break;
506            case 'u': uploadLimit = numarg( optarg ); break;
507            case 'U': uploadLimit = -1; break;
508            case 'v': verify = 1; break;
509            case 'w': downloadDir = optarg; break;
510            case TR_OPT_UNK: torrentPath = optarg; break;
511            default: return 1;
512        }
513    }
514
515    return 0;
516}
517
518static void
519sigHandler( int signal )
520{
521    switch( signal )
522    {
523        case SIGINT: gotsig = 1; break;
524        case SIGHUP: manualUpdate = 1; break;
525        default: break;
526    }
527}
Note: See TracBrowser for help on using the repository browser.