source: trunk/cli/cli.c @ 7151

Last change on this file since 7151 was 7151, checked in by charles, 12 years ago

(libT) add #ifdefs to ensure that client apps don't #include private libtransmission headers.

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