source: trunk/cli/cli.c @ 6308

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

fix r6307 typo

  • Property svn:keywords set to Date Rev Author Id
File size: 17.7 KB
Line 
1/******************************************************************************
2 * $Id: cli.c 6308 2008-07-09 15:53:26Z 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 LINEWIDTH 80
40#define MY_NAME "transmission-cli"
41
42static int           showInfo         = 0;
43static int           showScrape       = 0;
44static int           isPrivate        = 0;
45static int           verboseLevel     = 0;
46static int           encryptionMode   = TR_ENCRYPTION_PREFERRED;
47static int           peerPort         = TR_DEFAULT_PORT;
48static int           peerSocketTOS    = TR_DEFAULT_PEER_SOCKET_TOS;
49static int           blocklistEnabled = TR_DEFAULT_BLOCKLIST_ENABLED;
50static int           uploadLimit      = 20;
51static int           downloadLimit    = -1;
52static int           natTraversal     = TR_DEFAULT_PORT_FORWARDING_ENABLED;
53static int           verify           = 0;
54static sig_atomic_t  gotsig           = 0;
55static sig_atomic_t  manualUpdate     = 0;
56
57static const char   * torrentPath  = NULL;
58static const char   * downloadDir  = NULL;
59static const char   * finishCall   = NULL;
60static const char   * announce     = NULL;
61static const char   * configdir    = NULL;
62static const char   * sourceFile   = NULL;
63static const char   * comment      = NULL;
64
65static int  parseCommandLine ( int argc, const char ** argv );
66static void sigHandler       ( int signal );
67
68char*
69tr_strlratio( char * buf, double ratio, size_t buflen )
70{
71    if( (int)ratio == TR_RATIO_NA )
72        tr_strlcpy( buf, _( "None" ), buflen );
73    else if( (int)ratio == TR_RATIO_INF )
74        tr_strlcpy( buf, "Inf", buflen );
75    else if( ratio < 10.0 )
76        snprintf( buf, buflen, "%'.2f", ratio );
77    else if( ratio < 100.0 )
78        snprintf( buf, buflen, "%'.1f", ratio );
79    else
80        snprintf( buf, buflen, "%'.0f", ratio );
81    return buf;
82}
83
84static int
85is_rfc2396_alnum( char ch )
86{
87    return     ( '0' <= ch && ch <= '9' )
88            || ( 'A' <= ch && ch <= 'Z' )
89            || ( 'a' <= ch && ch <= 'z' );
90}
91
92static void
93escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
94{
95    const uint8_t *end = in + in_len;
96    while( in != end )
97        if( is_rfc2396_alnum(*in) )
98            *out++ = (char) *in++;
99        else
100            out += snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
101    *out = '\0';
102}
103
104static void
105torrentStateChanged( tr_torrent   * torrent UNUSED,
106                     cp_status_t    status UNUSED,
107                     void         * user_data UNUSED )
108{
109    system( finishCall );
110}
111
112static int leftToScrape = 0;
113
114static void
115scrapeDoneFunc( struct tr_handle    * session UNUSED,
116                long                  response_code,
117                const void          * response,
118                size_t                response_byte_count,
119                void                * host )
120{
121    tr_benc top, *files;
122
123    if( !tr_bencLoad( response, response_byte_count, &top, NULL ) 
124        && tr_bencDictFindDict( &top, "files", &files )
125        && files->val.l.count >= 2 )
126    {
127        int64_t complete=-1, incomplete=-1, downloaded=-1;
128        tr_benc * hash = &files->val.l.vals[1];
129        tr_bencDictFindInt( hash, "complete", &complete );
130        tr_bencDictFindInt( hash, "incomplete", &incomplete );
131        tr_bencDictFindInt( hash, "downloaded", &downloaded );
132        printf( "%4d seeders, %4d leechers, %5d downloads at %s\n",
133                (int)complete, (int)incomplete, (int)downloaded, (char*)host );
134        tr_bencFree( &top );
135    }
136    else
137        printf( "unable to parse response (http code %lu) at %s", response_code, (char*)host );
138
139    --leftToScrape;
140}
141
142static void
143dumpInfo( FILE * out, const tr_info * inf )
144{
145    int i;
146    int prevTier = -1;
147    tr_file_index_t ff;
148
149    fprintf( out, "hash:\t" );
150    for( i=0; i<SHA_DIGEST_LENGTH; ++i )
151        fprintf( out, "%02x", inf->hash[i] );
152    fprintf( out, "\n" );
153
154    fprintf( out, "name:\t%s\n", inf->name );
155
156    for( i=0; i<inf->trackerCount; ++i ) {
157        if( prevTier != inf->trackers[i].tier ) {
158            prevTier = inf->trackers[i].tier;
159            fprintf( out, "\ntracker tier #%d:\n", (prevTier+1) );
160        }
161        fprintf( out, "\tannounce:\t%s\n", inf->trackers[i].announce );
162    }
163
164    fprintf( out, "size:\t%"PRIu64" (%"PRIu64" * %d + %"PRIu64")\n",
165                  inf->totalSize, inf->totalSize / inf->pieceSize,
166                  inf->pieceSize, inf->totalSize % inf->pieceSize );
167
168    if( inf->comment && *inf->comment )
169        fprintf( out, "comment:\t%s\n", inf->comment );
170    if( inf->creator && *inf->creator )
171        fprintf( out, "creator:\t%s\n", inf->creator );
172    if( inf->isPrivate )
173        fprintf( out, "private flag set\n" );
174
175    fprintf( out, "file(s):\n" );
176    for( ff=0; ff<inf->fileCount; ++ff )
177        fprintf( out, "\t%s (%"PRIu64")\n", inf->files[ff].name,
178                                            inf->files[ff].length );
179}
180
181static void
182getStatusStr( const tr_stat * st, char * buf, size_t buflen )
183{
184    if( st->status & TR_STATUS_CHECK_WAIT )
185    {
186        snprintf( buf, buflen, "Waiting to verify local files" );
187    }
188    else if( st->status & TR_STATUS_CHECK )
189    {
190        snprintf( buf, buflen, "Verifying local files (%.2f%%, %.2f%% valid)",
191                  100 * st->recheckProgress, 100.0 * st->percentDone );
192    }
193    else if( st->status & TR_STATUS_DOWNLOAD )
194    {
195        char ratioStr[80];
196        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
197        snprintf( buf, buflen,
198                  "Progress: %.1f%%, dl from %d of %d peers (%.0f KB/s), "
199                  "ul to %d (%.0f KB/s) [%s]",
200                  st->percentDone * 100.0,
201                  st->peersSendingToUs,
202                  st->peersConnected,
203                  st->rateDownload,
204                  st->peersGettingFromUs,
205                  st->rateUpload,
206                  ratioStr );
207    }
208    else if( st->status & TR_STATUS_SEED )
209    {
210        char ratioStr[80];
211        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
212        snprintf( buf, buflen,
213                  "Seeding, uploading to %d of %d peer(s), %.0f KB/s [%s]",
214                  st->peersGettingFromUs, st->peersConnected,
215                  st->rateUpload, ratioStr );
216    }
217    else *buf = '\0';
218}
219
220int
221main( int argc, char ** argv )
222{
223    int error;
224    tr_handle  * h;
225    tr_ctor * ctor;
226    tr_torrent * tor = NULL;
227    char cwd[MAX_PATH_LENGTH];
228
229    printf( "Transmission %s - http://www.transmissionbt.com/\n",
230            LONG_VERSION_STRING );
231
232    /* Get options */
233    if( parseCommandLine( argc, (const char**)argv ) )
234        return EXIT_FAILURE;
235
236    /* Check the options for validity */
237    if( !torrentPath ) {
238        printf( "No torrent specified!\n" );
239        return EXIT_FAILURE;
240    }
241    if( peerPort < 1 || peerPort > 65535 ) {
242        printf( "Error: Port must between 1 and 65535; got %d\n", peerPort );
243        return EXIT_FAILURE;
244    }
245    if( peerSocketTOS < 0 || peerSocketTOS > 255 ) {
246        printf( "Error: value must between 0 and 255; got %d\n", peerSocketTOS );
247        return EXIT_FAILURE;
248    }
249
250    /* don't bind the port if we're just running the CLI
251     * to get metainfo or to create a torrent */
252    if( showInfo || showScrape || ( sourceFile != NULL ) )
253        peerPort = -1;
254
255    if( configdir == NULL )
256        configdir = tr_getDefaultConfigDir( );
257
258    /* if no download directory specified, use cwd instead */
259    if( !downloadDir ) {
260        getcwd( cwd, sizeof( cwd ) );
261        downloadDir = cwd;
262    }
263
264
265    /* Initialize libtransmission */
266    h = tr_sessionInitFull(
267            configdir,
268            "cli",                         /* tag */
269            downloadDir,                   /* where to download torrents */
270            TR_DEFAULT_PEX_ENABLED,
271            natTraversal,                  /* nat enabled */
272            peerPort,
273            encryptionMode,
274            uploadLimit >= 0,
275            uploadLimit,
276            downloadLimit >= 0,
277            downloadLimit,
278            TR_DEFAULT_GLOBAL_PEER_LIMIT,
279            verboseLevel + 1,              /* messageLevel */
280            0,                             /* is message queueing enabled? */
281            blocklistEnabled,
282            peerSocketTOS,
283            TR_DEFAULT_RPC_ENABLED,
284            TR_DEFAULT_RPC_PORT,
285            TR_DEFAULT_RPC_ACL,
286            FALSE, "fnord", "potzrebie",
287            TR_DEFAULT_PROXY_ENABLED,
288            TR_DEFAULT_PROXY,
289            TR_DEFAULT_PROXY_TYPE,
290            TR_DEFAULT_PROXY_AUTH_ENABLED,
291            TR_DEFAULT_PROXY_USERNAME,
292            TR_DEFAULT_PROXY_PASSWORD );
293
294    if( sourceFile && *sourceFile ) /* creating a torrent */
295    {
296        int err;
297        tr_metainfo_builder * b = tr_metaInfoBuilderCreate( h, sourceFile );
298        tr_tracker_info ti;
299        ti.tier = 0;
300        ti.announce = (char*) announce;
301        tr_makeMetaInfo( b, torrentPath, &ti, 1, comment, isPrivate );
302        while( !b->isDone ) {
303            tr_wait( 1000 );
304            printf( "." );
305        }
306        err = b->result;
307        tr_metaInfoBuilderFree( b );
308        return err;
309    }
310
311    ctor = tr_ctorNew( h );
312    tr_ctorSetMetainfoFromFile( ctor, torrentPath );
313    tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
314    tr_ctorSetDownloadDir( ctor, TR_FORCE, downloadDir );
315
316    if( showScrape )
317    {
318        tr_info info;
319
320        if( !tr_torrentParse( h, ctor, &info ) )
321        {
322            int i;
323            const time_t start = time( NULL );
324            for( i=0; i<info.trackerCount; ++i )
325            {
326                if( info.trackers[i].scrape )
327                {
328                    const char * scrape = info.trackers[i].scrape;
329                    char escaped[SHA_DIGEST_LENGTH*3 + 1];
330                    char *url, *host;
331                    escape( escaped, info.hash, SHA_DIGEST_LENGTH );
332                    url = tr_strdup_printf( "%s%cinfo_hash=%s",
333                                            scrape,
334                                            strchr(scrape,'?')?'&':'?',
335                                            escaped );
336                    tr_httpParseURL( scrape, -1, &host, NULL, NULL );
337                    ++leftToScrape;
338                    tr_webRun( h, url, NULL, scrapeDoneFunc, host );
339                    tr_free( host );
340                    tr_free( url );
341                }
342            }
343
344            fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
345
346            while( leftToScrape>0 && ((time(NULL)-start)<20) )
347                tr_wait( 250 );
348        }
349        goto cleanup;
350    }
351
352    if( showInfo )
353    {
354        tr_info info;
355
356        if( !tr_torrentParse( h, ctor, &info ) )
357        {
358            dumpInfo( stdout, &info );
359            tr_metainfoFree( &info );
360        }
361
362        tr_ctorFree( ctor );
363        goto cleanup;
364    }
365
366    tor = tr_torrentNew( h, ctor, &error );
367    tr_ctorFree( ctor );
368    if( !tor )
369    {
370        printf( "Failed opening torrent file `%s'\n", torrentPath );
371        tr_sessionClose( h );
372        return EXIT_FAILURE;
373    }
374
375    signal( SIGINT, sigHandler );
376    signal( SIGHUP, sigHandler );
377
378    tr_torrentSetStatusCallback( tor, torrentStateChanged, NULL );
379    tr_torrentStart( tor );
380
381    if( verify ) {
382        verify = 0;
383        tr_torrentVerify( tor );
384    }
385
386    for( ;; )
387    {
388        char line[LINEWIDTH];
389        const tr_stat * st;
390
391        tr_wait( 1000 );
392
393        if( gotsig ) {
394            gotsig = 0;
395            printf( "\nStopping torrent...\n" );
396            tr_torrentStop( tor );
397        }
398       
399        if( manualUpdate ) {
400            manualUpdate = 0;
401            if ( !tr_torrentCanManualUpdate( tor ) )
402                fprintf( stderr, "\nReceived SIGHUP, but can't send a manual update now\n" );
403            else {
404                fprintf( stderr, "\nReceived SIGHUP: manual update scheduled\n" );
405                tr_torrentManualUpdate( tor );
406            }
407        }
408
409        st = tr_torrentStat( tor );
410        if( st->status & TR_STATUS_STOPPED )
411            break;
412
413        getStatusStr( st, line, sizeof( line ) );
414        printf( "\r%-*s", LINEWIDTH, line );
415        if( st->error )
416            printf( "\n%s\n", st->errorString );
417    }
418
419cleanup:
420    printf( "\n" );
421    tr_sessionClose( h );
422    return EXIT_SUCCESS;
423}
424
425/***
426****
427****
428****
429***/
430
431static const char *
432getUsage( void )
433{
434    return "A fast and easy BitTorrent client\n"
435           "\n"
436           "Usage: "MY_NAME" [options] <torrent-filename>";
437}
438
439const struct tr_option options[] = {
440    { 'a', "announce", "Set the new torrent's announce URL", "a", 1, "<url>" },
441    { 'b', "blocklist", "Enable peer blocklists", "b", 0, NULL },
442    { 'B', "no-blocklist", "Disable peer blocklists", "B", 0, NULL },
443    { 'c', "comment", "Set the new torrent's comment", "c", 1, "<comment>" },
444    { 'd', "downlimit", "Set max download speed in KB/s", "d", 1, "<number>" },
445    { 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL },
446    { 'e', "encryption", "Set encryption mode (required, preferred, tolerated)",
447      "e", 1, "<mode>" },
448    { 'f', "finish", "Set a script to run when the torrent finishes",
449      "f", 1, "<script>" },
450    { 'g', "config-dir", "Where to find configuration files",
451      "g", 1, "<path>" },
452    { 'i', "info", "Show torrent details and exit", "i", 0, NULL },
453    { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
454    { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
455    { 'n', "new", "Create a new torrent from a file or directory",
456      "n", 1, "<path>" },
457    { 'p', "port",
458      "Port to listen for incoming peers (Default: "TR_DEFAULT_PORT_STR")",
459      "p", 1, "<port>" },
460    { 'r', "private", "Set the new torrent's 'private' flag", "r", 0, NULL },
461    { 's', "scrape", "Scrape the torrent and exit", "s", 0, NULL },
462    { 't', "tos",
463      "Peer socket TOS (0 to 255, default="TR_DEFAULT_PEER_SOCKET_TOS_STR")",
464      "t", 1, "<number>" },
465    { 'u', "uplimit", "Set max upload speed in KB/s", "u", 1, "<number>" },
466    { 'U', "no-uplimit", "Don't limit the upload speed", "U", 0, NULL },
467    { 'v', "verify", "Verify the specified torrent", "v", 0, NULL },
468    { 'w', "download-dir", "Where to save downloaded data", "w", 1, "<path>" },
469    { 0, NULL, NULL, NULL, 0, NULL }
470};
471
472static void
473showUsage( void )
474{
475    tr_getopt_usage( MY_NAME, getUsage(), options );
476    exit( 0 );
477}
478
479static int
480numarg( const char * arg )
481{
482    char * end = NULL;
483    const long num = strtol( arg, &end, 10 );
484    if( *end ) {
485        fprintf( stderr, "Not a number: \"%s\"\n", arg );
486        showUsage( );
487    }
488    return num;
489}
490
491static int
492parseCommandLine( int argc, const char ** argv )
493{
494    int c;
495    const char * optarg;
496
497    while(( c = tr_getopt( getUsage(), argc, argv, options, &optarg )))
498    {
499        switch( c )
500        {
501            case 'a': announce = optarg; break;
502            case 'b': blocklistEnabled = 1; break;
503            case 'B': blocklistEnabled = 0; break;
504            case 'c': comment = optarg; break;
505            case 'd': downloadLimit = numarg( optarg ); break;
506            case 'D': downloadLimit = -1; break;
507            case 'e': if( !strcmp( optarg, "required" ) )
508                          encryptionMode = TR_ENCRYPTION_REQUIRED;
509                      else if( !strcmp( optarg, "tolerated" ) )
510                          encryptionMode = TR_PLAINTEXT_PREFERRED;
511                      else
512                          encryptionMode = TR_ENCRYPTION_PREFERRED;
513                      break;
514            case 'f': finishCall = optarg; break;
515            case 'g': configdir = optarg; break;
516            case 'i': showInfo = 1; break;
517            case 'm': natTraversal = 1; break;
518            case 'M': natTraversal = 0; break;
519            case 'n': sourceFile = optarg; break;
520            case 'p': peerPort = numarg( optarg ); break;
521            case 'r': isPrivate = 1; break;
522            case 's': showScrape = 1; break;
523            case 't': peerSocketTOS = numarg( optarg ); break;
524            case 'u': uploadLimit = numarg( optarg ); break;
525            case 'U': uploadLimit = -1; break;
526            case 'v': verify = 1; break;
527            case 'w': downloadDir = optarg; break;
528            case TR_OPT_UNK: torrentPath = optarg; break;
529            default: return 1;
530        }
531    }
532
533    return 0;
534}
535
536static void
537sigHandler( int signal )
538{
539    switch( signal )
540    {
541        case SIGINT: gotsig = 1; break;
542        case SIGHUP: manualUpdate = 1; break;
543        default: break;
544    }
545}
Note: See TracBrowser for help on using the repository browser.