source: trunk/cli/cli.c @ 8233

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

(trunk) make it possible to #include "version.h" without having to add -I${TOP}/libtransmission/ to your CFLAGS

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