source: trunk/cli/cli.c @ 8050

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

(trunk libT) fix getConfigDir() bug reported by wereHamster

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