source: trunk/cli/cli.c @ 7069

Last change on this file since 7069 was 7069, checked in by charles, 10 years ago

more fucking around with the speed measurements.

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