source: trunk/cli/cli.c @ 6496

Last change on this file since 6496 was 6496, checked in by charles, 13 years ago

readability improvments #1, #2, #3, #4. (muks)

  • Property svn:keywords set to Date Rev Author Id
File size: 17.7 KB
Line 
1/******************************************************************************
2 * $Id: cli.c 6496 2008-08-12 13:51:11Z 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
68static char*
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        tr_snprintf( buf, buflen, "%'.2f", ratio );
77    else if( ratio < 100.0 )
78        tr_snprintf( buf, buflen, "%'.1f", ratio );
79    else
80        tr_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 += tr_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        tr_snprintf( buf, buflen, "Waiting to verify local files" );
187    }
188    else if( st->status & TR_STATUS_CHECK )
189    {
190        tr_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        tr_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        tr_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_PORT,
290            TR_DEFAULT_PROXY_TYPE,
291            TR_DEFAULT_PROXY_AUTH_ENABLED,
292            TR_DEFAULT_PROXY_USERNAME,
293            TR_DEFAULT_PROXY_PASSWORD );
294
295    if( sourceFile && *sourceFile ) /* creating a torrent */
296    {
297        int err;
298        tr_metainfo_builder * b = tr_metaInfoBuilderCreate( h, sourceFile );
299        tr_tracker_info ti;
300        ti.tier = 0;
301        ti.announce = (char*) announce;
302        tr_makeMetaInfo( b, torrentPath, &ti, 1, comment, isPrivate );
303        while( !b->isDone ) {
304            tr_wait( 1000 );
305            printf( "." );
306        }
307        err = b->result;
308        tr_metaInfoBuilderFree( b );
309        return err;
310    }
311
312    ctor = tr_ctorNew( h );
313    tr_ctorSetMetainfoFromFile( ctor, torrentPath );
314    tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
315    tr_ctorSetDownloadDir( ctor, TR_FORCE, downloadDir );
316
317    if( showScrape )
318    {
319        tr_info info;
320
321        if( !tr_torrentParse( h, ctor, &info ) )
322        {
323            int i;
324            const time_t start = time( NULL );
325            for( i=0; i<info.trackerCount; ++i )
326            {
327                if( info.trackers[i].scrape )
328                {
329                    const char * scrape = info.trackers[i].scrape;
330                    char escaped[SHA_DIGEST_LENGTH*3 + 1];
331                    char *url, *host;
332                    escape( escaped, info.hash, SHA_DIGEST_LENGTH );
333                    url = tr_strdup_printf( "%s%cinfo_hash=%s",
334                                            scrape,
335                                            strchr(scrape,'?')?'&':'?',
336                                            escaped );
337                    tr_httpParseURL( scrape, -1, &host, NULL, NULL );
338                    ++leftToScrape;
339                    tr_webRun( h, url, NULL, scrapeDoneFunc, host );
340                    tr_free( host );
341                    tr_free( url );
342                }
343            }
344
345            fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
346
347            while( leftToScrape>0 && ((time(NULL)-start)<20) )
348                tr_wait( 250 );
349        }
350        goto cleanup;
351    }
352
353    if( showInfo )
354    {
355        tr_info info;
356
357        if( !tr_torrentParse( h, ctor, &info ) )
358        {
359            dumpInfo( stdout, &info );
360            tr_metainfoFree( &info );
361        }
362
363        tr_ctorFree( ctor );
364        goto cleanup;
365    }
366
367    tor = tr_torrentNew( h, ctor, &error );
368    tr_ctorFree( ctor );
369    if( !tor )
370    {
371        printf( "Failed opening torrent file `%s'\n", torrentPath );
372        tr_sessionClose( h );
373        return EXIT_FAILURE;
374    }
375
376    signal( SIGINT, sigHandler );
377    signal( SIGHUP, sigHandler );
378
379    tr_torrentSetStatusCallback( tor, torrentStateChanged, NULL );
380    tr_torrentStart( tor );
381
382    if( verify ) {
383        verify = 0;
384        tr_torrentVerify( tor );
385    }
386
387    for( ;; )
388    {
389        char line[LINEWIDTH];
390        const tr_stat * st;
391
392        tr_wait( 200 );
393
394        if( gotsig ) {
395            gotsig = 0;
396            printf( "\nStopping torrent...\n" );
397            tr_torrentStop( tor );
398        }
399       
400        if( manualUpdate ) {
401            manualUpdate = 0;
402            if ( !tr_torrentCanManualUpdate( tor ) )
403                fprintf( stderr, "\nReceived SIGHUP, but can't send a manual update now\n" );
404            else {
405                fprintf( stderr, "\nReceived SIGHUP: manual update scheduled\n" );
406                tr_torrentManualUpdate( tor );
407            }
408        }
409
410        st = tr_torrentStat( tor );
411        if( st->status & TR_STATUS_STOPPED )
412            break;
413
414        getStatusStr( st, line, sizeof( line ) );
415        printf( "\r%-*s", LINEWIDTH, line );
416        if( st->error )
417            printf( "\n%s\n", st->errorString );
418    }
419
420cleanup:
421    printf( "\n" );
422    tr_sessionClose( h );
423    return EXIT_SUCCESS;
424}
425
426/***
427****
428****
429****
430***/
431
432static const char *
433getUsage( void )
434{
435    return "A fast and easy BitTorrent client\n"
436           "\n"
437           "Usage: "MY_NAME" [options] <torrent-filename>";
438}
439
440static const struct tr_option options[] = {
441    { 'a', "announce", "Set the new torrent's announce URL", "a", 1, "<url>" },
442    { 'b', "blocklist", "Enable peer blocklists", "b", 0, NULL },
443    { 'B', "no-blocklist", "Disable peer blocklists", "B", 0, NULL },
444    { 'c', "comment", "Set the new torrent's comment", "c", 1, "<comment>" },
445    { 'd', "downlimit", "Set max download speed in KB/s", "d", 1, "<speed>" },
446    { 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL },
447    { 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL },
448    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
449    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
450    { 'f', "finish", "Run a script when the torrent finishes",
451      "f", 1, "<script>" },
452    { 'g', "config-dir", "Where to find configuration files",
453      "g", 1, "<path>" },
454    { 'i', "info", "Show torrent details and exit", "i", 0, NULL },
455    { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
456    { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
457    { 'n', "new", "Create a new torrent",
458      "n", 1, "<source>" },
459    { 'p', "port",
460      "Port for incoming peers (Default: "TR_DEFAULT_PORT_STR")",
461      "p", 1, "<port>" },
462    { 'r', "private", "Set the new torrent's 'private' flag", "r", 0, NULL },
463    { 's', "scrape", "Scrape the torrent and exit", "s", 0, NULL },
464    { 't', "tos",
465      "Peer socket TOS (0 to 255, default="TR_DEFAULT_PEER_SOCKET_TOS_STR")",
466      "t", 1, "<tos>" },
467    { 'u', "uplimit", "Set max upload speed in KB/s", "u", 1, "<speed>" },
468    { 'U', "no-uplimit", "Don't limit the upload speed", "U", 0, NULL },
469    { 'v', "verify", "Verify the specified torrent", "v", 0, NULL },
470    { 'w', "download-dir", "Where to save downloaded data", "w", 1, "<path>" },
471    { 0, NULL, NULL, NULL, 0, NULL }
472};
473
474static void
475showUsage( void )
476{
477    tr_getopt_usage( MY_NAME, getUsage(), options );
478    exit( 0 );
479}
480
481static int
482numarg( const char * arg )
483{
484    char * end = NULL;
485    const long num = strtol( arg, &end, 10 );
486    if( *end ) {
487        fprintf( stderr, "Not a number: \"%s\"\n", arg );
488        showUsage( );
489    }
490    return num;
491}
492
493static int
494parseCommandLine( int argc, const char ** argv )
495{
496    int c;
497    const char * optarg;
498
499    while(( c = tr_getopt( getUsage(), argc, argv, options, &optarg )))
500    {
501        switch( c )
502        {
503            case 'a': announce = optarg; break;
504            case 'b': blocklistEnabled = 1; break;
505            case 'B': blocklistEnabled = 0; break;
506            case 'c': comment = optarg; break;
507            case 'd': downloadLimit = numarg( optarg ); break;
508            case 'D': downloadLimit = -1; break;
509            case 'f': finishCall = optarg; break;
510            case 'g': configdir = optarg; break;
511            case 'i': showInfo = 1; break;
512            case 'm': natTraversal = 1; break;
513            case 'M': natTraversal = 0; break;
514            case 'n': sourceFile = optarg; break;
515            case 'p': peerPort = numarg( optarg ); break;
516            case 'r': isPrivate = 1; break;
517            case 's': showScrape = 1; break;
518            case 't': peerSocketTOS = numarg( optarg ); break;
519            case 'u': uploadLimit = numarg( optarg ); break;
520            case 'U': uploadLimit = -1; break;
521            case 'v': verify = 1; break;
522            case 'w': downloadDir = optarg; break;
523            case 910: encryptionMode = TR_ENCRYPTION_REQUIRED; break;
524            case 911: encryptionMode = TR_CLEAR_PREFERRED; break;
525            case 912: encryptionMode = TR_ENCRYPTION_PREFERRED; break;
526            case TR_OPT_UNK: torrentPath = optarg; break;
527            default: return 1;
528        }
529    }
530
531    return 0;
532}
533
534static void
535sigHandler( int signal )
536{
537    switch( signal )
538    {
539        case SIGINT: gotsig = 1; break;
540        case SIGHUP: manualUpdate = 1; break;
541        default: break;
542    }
543}
Note: See TracBrowser for help on using the repository browser.