source: branches/1.2x/cli/transmissioncli.c @ 6228

Last change on this file since 6228 was 6228, checked in by charles, 14 years ago

(1.2x) #1044: fix bug with the -o command-line option

  • Property svn:keywords set to Date Rev Author Id
File size: 17.3 KB
Line 
1/******************************************************************************
2 * $Id: transmissioncli.c 6228 2008-06-19 13:06:24Z 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 <getopt.h>
30#include <signal.h>
31
32#include <libtransmission/transmission.h>
33#include <libtransmission/bencode.h>
34#include <libtransmission/makemeta.h>
35#include <libtransmission/metainfo.h> /* tr_metainfoFree */
36#include <libtransmission/utils.h> /* tr_wait */
37#include <libtransmission/web.h> /* tr_webRun */
38
39/* macro to shut up "unused parameter" warnings */
40#ifdef __GNUC__
41#define UNUSED                  __attribute__((unused))
42#else
43#define UNUSED
44#endif
45
46const char * USAGE =
47"Usage: %s [-car[-m]] [-dfginpsuv] [-h] file.torrent [output-dir]\n\n"
48"Options:\n"
49"  -h, --help                Print this help and exit\n" 
50"  -i, --info                Print metainfo and exit\n"
51"  -s, --scrape              Print counts of seeders/leechers and exit\n"
52"  -V, --version             Print the version number and exit\n"
53"  -c, --create-from <file>  Create torrent from the specified source file.\n"
54"  -a, --announce <url>      Used in conjunction with -c.\n"
55"  -g, --config-dir <path>   Where to look for configuration files\n"
56"  -o, --output-dir <path>   Where to save downloaded data\n"
57"  -r, --private             Used in conjunction with -c.\n"
58"  -m, --comment <text>      Adds an optional comment when creating a torrent.\n"
59"  -d, --download <int>      Max download rate (-1 = no limit, default = -1)\n"
60"  -f, --finish <script>     Command you wish to run on completion\n" 
61"  -n  --nat-traversal       Attempt NAT traversal using NAT-PMP or UPnP IGD\n"
62"  -p, --port <int>          Port we should listen on (default = %d)\n"
63"  -u, --upload <int>        Maximum upload rate (-1 = no limit, default = 20)\n"
64"  -v, --verbose <int>       Verbose level (0 to 2, default = 0)\n"
65"  -y, --recheck             Force a recheck of the torrent data\n";
66
67static int           showHelp      = 0;
68static int           showInfo      = 0;
69static int           showScrape    = 0;
70static int           showVersion   = 0;
71static int           isPrivate     = 0;
72static int           verboseLevel  = 0;
73static int           bindPort      = TR_DEFAULT_PORT;
74static int           uploadLimit   = 20;
75static int           downloadLimit = -1;
76static char        * torrentPath   = NULL;
77static char        * savePath      = ".";
78static int           natTraversal  = 0;
79static int           recheckData   = 0;
80static sig_atomic_t  gotsig        = 0;
81static sig_atomic_t  manualUpdate  = 0;
82
83static char          * finishCall   = NULL;
84static char          * announce     = NULL;
85static char          * configdir    = NULL;
86static char          * sourceFile   = NULL;
87static char          * comment      = NULL;
88
89static int  parseCommandLine ( int argc, char ** argv );
90static void sigHandler       ( int signal );
91
92static char *
93getStringRatio( float ratio )
94{
95    static char string[20];
96
97    if( ratio == TR_RATIO_NA )
98        return "n/a";
99    snprintf( string, sizeof string, "%.3f", ratio );
100    return string;
101}
102
103static int
104is_rfc2396_alnum( char ch )
105{
106    return     ( '0' <= ch && ch <= '9' )
107            || ( 'A' <= ch && ch <= 'Z' )
108            || ( 'a' <= ch && ch <= 'z' );
109}
110
111static void
112escape( char * out, const uint8_t * in, int in_len ) /* rfc2396 */
113{
114    const uint8_t *end = in + in_len;
115    while( in != end )
116        if( is_rfc2396_alnum(*in) )
117            *out++ = (char) *in++;
118        else
119            out += snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
120    *out = '\0';
121}
122
123#define LINEWIDTH 80
124
125static void
126torrentStateChanged( tr_torrent   * torrent UNUSED,
127                     cp_status_t    status UNUSED,
128                     void         * user_data UNUSED )
129{
130    system( finishCall );
131}
132
133static int leftToScrape = 0;
134
135static void
136scrapeDoneFunc( struct tr_handle    * session UNUSED,
137                long                  response_code,
138                const void          * response,
139                size_t                response_byte_count,
140                void                * host )
141{
142    tr_benc top, *files;
143
144    if( !tr_bencLoad( response, response_byte_count, &top, NULL ) 
145        && tr_bencDictFindDict( &top, "files", &files )
146        && files->val.l.count >= 2 )
147    {
148        int64_t complete=-1, incomplete=-1, downloaded=-1;
149        tr_benc * hash = &files->val.l.vals[1];
150        tr_bencDictFindInt( hash, "complete", &complete );
151        tr_bencDictFindInt( hash, "incomplete", &incomplete );
152        tr_bencDictFindInt( hash, "downloaded", &downloaded );
153        printf( "%4d seeders, %4d leechers, %5d downloads at %s\n",
154                (int)complete, (int)incomplete, (int)downloaded, (char*)host );
155        tr_bencFree( &top );
156    }
157    else
158        printf( "unable to parse response (http code %lu) at %s", response_code, (char*)host );
159
160    --leftToScrape;
161}
162
163int
164main( int argc, char ** argv )
165{
166    int i, error;
167    tr_handle  * h;
168    tr_ctor * ctor;
169    tr_torrent * tor = NULL;
170
171    printf( "Transmission %s - http://www.transmissionbt.com/\n",
172            LONG_VERSION_STRING );
173
174    /* Get options */
175    if( parseCommandLine( argc, argv ) )
176    {
177        printf( USAGE, argv[0], TR_DEFAULT_PORT );
178        return EXIT_FAILURE;
179    }
180
181    if( showVersion )
182        return EXIT_SUCCESS;
183
184    if( showHelp )
185    {
186        printf( USAGE, argv[0], TR_DEFAULT_PORT );
187        return EXIT_SUCCESS;
188    }
189
190    if( bindPort < 1 || bindPort > 65535 )
191    {
192        printf( "Invalid port '%d'\n", bindPort );
193        return EXIT_FAILURE;
194    }
195
196    /* don't bind the port if we're just running the CLI
197     * to get metainfo or to create a torrent */
198    if( showInfo || showScrape || ( sourceFile != NULL ) )
199        bindPort = -1;
200
201    if( configdir == NULL )
202        configdir = strdup( tr_getDefaultConfigDir( ) );
203
204    /* Initialize libtransmission */
205    h = tr_initFull( configdir,
206                     "cli",                         /* tag */
207                     1,                             /* pex enabled */
208                     natTraversal,                  /* nat enabled */
209                     bindPort,                      /* public port */
210                     TR_ENCRYPTION_PREFERRED,       /* encryption mode */
211                     uploadLimit >= 0,              /* use upload speed limit? */
212                     uploadLimit,                   /* upload speed limit */
213                     downloadLimit >= 0,            /* use download speed limit? */
214                     downloadLimit,                 /* download speed limit */
215                     TR_DEFAULT_GLOBAL_PEER_LIMIT,
216                     verboseLevel + 1,              /* messageLevel */
217                     0,                             /* is message queueing enabled? */
218                     0,                             /* use the blocklist? */
219                     TR_DEFAULT_PEER_SOCKET_TOS );
220
221    if( sourceFile && *sourceFile ) /* creating a torrent */
222    {
223        int err;
224        tr_metainfo_builder * builder = tr_metaInfoBuilderCreate( h, sourceFile );
225        tr_makeMetaInfo( builder, torrentPath, announce, comment, isPrivate );
226        while( !builder->isDone ) {
227            tr_wait( 1000 );
228            printf( "." );
229        }
230        err = builder->result;
231        tr_metaInfoBuilderFree( builder );
232        return err;
233    }
234
235    ctor = tr_ctorNew( h );
236    tr_ctorSetMetainfoFromFile( ctor, torrentPath );
237    tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
238    tr_ctorSetDestination( ctor, TR_FORCE, savePath );
239
240    if( showScrape )
241    {
242        tr_info info;
243
244        if( !tr_torrentParse( h, ctor, &info ) )
245        {
246            int i;
247            const time_t start = time( NULL );
248            for( i=0; i<info.trackerCount; ++i )
249            {
250                if( info.trackers[i].scrape )
251                {
252                    const char * scrape = info.trackers[i].scrape;
253                    char escaped[SHA_DIGEST_LENGTH*3 + 1];
254                    char url[2048], *host;
255                    escape( escaped, info.hash, SHA_DIGEST_LENGTH );
256                    snprintf( url, sizeof(url), "%s%cinfo_hash=%s",
257                                                scrape,
258                                                strchr(scrape,'?')?'&':'?',
259                                                escaped );
260                    tr_httpParseURL( scrape, -1, &host, NULL, NULL );
261                    ++leftToScrape;
262                    tr_webRun( h, url, scrapeDoneFunc, host );
263                }
264            }
265
266            fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
267
268            while( leftToScrape>0 && ((time(NULL)-start)<20) )
269                tr_wait( 250 );
270        }
271        goto cleanup;
272    }
273
274
275    if( showInfo )
276    {
277        tr_info info;
278
279        if( !tr_torrentParse( h, ctor, &info ) )
280        {
281            int prevTier = -1;
282            tr_file_index_t ff;
283
284            printf( "hash:\t" );
285            for( i=0; i<SHA_DIGEST_LENGTH; ++i )
286                printf( "%02x", info.hash[i] );
287            printf( "\n" );
288
289            printf( "name:\t%s\n", info.name );
290
291            for( i=0; i<info.trackerCount; ++i ) {
292                if( prevTier != info.trackers[i].tier ) {
293                    prevTier = info.trackers[i].tier;
294                    printf( "\ntracker tier #%d:\n", (prevTier+1) );
295                }
296                printf( "\tannounce:\t%s\n", info.trackers[i].announce );
297            }
298
299            printf( "size:\t%"PRIu64" (%"PRIu64" * %d + %"PRIu64")\n",
300                    info.totalSize, info.totalSize / info.pieceSize,
301                    info.pieceSize, info.totalSize % info.pieceSize );
302
303            if( info.comment[0] )
304                printf( "comment:\t%s\n", info.comment );
305            if( info.creator[0] )
306                printf( "creator:\t%s\n", info.creator );
307            if( info.isPrivate )
308                printf( "private flag set\n" );
309
310            printf( "file(s):\n" );
311            for( ff=0; ff<info.fileCount; ++ff )
312                printf( "\t%s (%"PRIu64")\n", info.files[ff].name, info.files[ff].length );
313
314            tr_metainfoFree( &info );
315        }
316
317        tr_ctorFree( ctor );
318        goto cleanup;
319    }
320
321
322    tor = tr_torrentNew( h, ctor, &error );
323    tr_ctorFree( ctor );
324    if( tor == NULL )
325    {
326        printf( "Failed opening torrent file `%s'\n", torrentPath );
327        tr_close( h );
328        return EXIT_FAILURE;
329    }
330
331    signal( SIGINT, sigHandler );
332    signal( SIGHUP, sigHandler );
333
334    tr_torrentSetStatusCallback( tor, torrentStateChanged, NULL );
335    tr_torrentStart( tor );
336
337    for( ;; )
338    {
339        char string[LINEWIDTH];
340        int  chars = 0;
341        const struct tr_stat * s;
342
343        tr_wait( 1000 );
344
345        if( gotsig )
346        {
347            gotsig = 0;
348            tr_torrentStop( tor );
349            tr_natTraversalEnable( h, 0 );
350        }
351       
352        if( manualUpdate )
353        {
354            manualUpdate = 0;
355            if ( !tr_torrentCanManualUpdate( tor ) )
356                fprintf( stderr, "\rReceived SIGHUP, but can't send a manual update now\n" );
357            else {
358                fprintf( stderr, "\rReceived SIGHUP: manual update scheduled\n" );
359                tr_manualUpdate( tor );
360            }
361        }
362       
363        if( recheckData )
364        {
365            recheckData = 0;
366            tr_torrentVerify( tor );
367        }
368
369        s = tr_torrentStat( tor );
370
371        if( s->status & TR_STATUS_CHECK_WAIT )
372        {
373            chars = snprintf( string, sizeof string,
374                "Waiting to verify local files..." );
375        }
376        else if( s->status & TR_STATUS_CHECK )
377        {
378            chars = snprintf( string, sizeof string,
379                "Verifying local files... %.2f%%, found %.2f%% valid", 100 * s->recheckProgress, 100.0 * s->percentDone );
380        }
381        else if( s->status & TR_STATUS_DOWNLOAD )
382        {
383            chars = snprintf( string, sizeof string,
384                "Progress: %.2f %%, %d peer%s, dl from %d (%.2f KB/s), "
385                "ul to %d (%.2f KB/s) [%s]", 100.0 * s->percentDone,
386                s->peersConnected, ( s->peersConnected == 1 ) ? "" : "s",
387                s->peersSendingToUs, s->rateDownload,
388                s->peersGettingFromUs, s->rateUpload,
389                getStringRatio(s->ratio) );
390        }
391        else if( s->status & TR_STATUS_SEED )
392        {
393            chars = snprintf( string, sizeof string,
394                "Seeding, uploading to %d of %d peer(s), %.2f KB/s [%s]",
395                s->peersGettingFromUs, s->peersConnected,
396                s->rateUpload, getStringRatio(s->ratio) );
397        }
398        else if( s->status & TR_STATUS_STOPPED )
399        {
400            break;
401        }
402        if( ( signed )sizeof string > chars )
403        {
404            memset( &string[chars], ' ', sizeof string - 1 - chars );
405        }
406        string[sizeof string - 1] = '\0';
407        fprintf( stderr, "\r%s", string );
408
409        if( s->error )
410        {
411            fprintf( stderr, "\n%s\n", s->errorString );
412        }
413        else if( verboseLevel > 0 )
414        {
415            fprintf( stderr, "\n" );
416        }
417    }
418    fprintf( stderr, "\n" );
419
420    /* Try for 5 seconds to delete any port mappings for nat traversal */
421    tr_natTraversalEnable( h, 0 );
422    for( i = 0; i < 10; i++ )
423    {
424        const tr_handle_status * hstat = tr_handleStatus( h );
425        if( TR_NAT_TRAVERSAL_UNMAPPED == hstat->natTraversalStatus )
426        {
427            /* Port mappings were deleted */
428            break;
429        }
430        tr_wait( 500 );
431    }
432   
433cleanup:
434    tr_torrentClose( tor );
435    tr_close( h );
436
437    return EXIT_SUCCESS;
438}
439
440static int
441parseCommandLine( int argc, char ** argv )
442{
443    for( ;; )
444    {
445        static const struct option long_options[] = {
446            { "announce",      required_argument, NULL, 'a' },
447            { "create-from",   required_argument, NULL, 'c' },
448            { "download",      required_argument, NULL, 'd' },
449            { "finish",        required_argument, NULL, 'f' },
450            { "config-dir",    required_argument, NULL, 'g' },
451            { "help",          no_argument,       NULL, 'h' },
452            { "info",          no_argument,       NULL, 'i' },
453            { "comment",       required_argument, NULL, 'm' },
454            { "nat-traversal", no_argument,       NULL, 'n' },
455            { "output-dir",    required_argument, NULL, 'o' },
456            { "port",          required_argument, NULL, 'p' },
457            { "private",       no_argument,       NULL, 'r' },
458            { "scrape",        no_argument,       NULL, 's' },
459            { "upload",        required_argument, NULL, 'u' },
460            { "verbose",       required_argument, NULL, 'v' },
461            { "version",       no_argument,       NULL, 'V' },
462            { "recheck",       no_argument,       NULL, 'y' },
463            { 0, 0, 0, 0} };
464        int optind = 0;
465        int c = getopt_long( argc, argv,
466                             "a:c:d:f:g:him:no:p:rsu:v:Vy",
467                             long_options, &optind );
468        if( c < 0 )
469        {
470            break;
471        }
472        switch( c )
473        {
474            case 'a': announce = optarg; break;
475            case 'c': sourceFile = optarg; break;
476            case 'd': downloadLimit = atoi( optarg ); break;
477            case 'f': finishCall = optarg; break;
478            case 'g': configdir = strdup( optarg ); break;
479            case 'h': showHelp = 1; break;
480            case 'i': showInfo = 1; break;
481            case 'm': comment = optarg; break;
482            case 'n': natTraversal = 1; break;
483            case 'o': savePath = optarg; break;
484            case 'p': bindPort = atoi( optarg ); break;
485            case 'r': isPrivate = 1; break;
486            case 's': showScrape = 1; break;
487            case 'u': uploadLimit = atoi( optarg ); break;
488            case 'v': verboseLevel = atoi( optarg ); break;
489            case 'V': showVersion = 1; break;
490            case 'y': recheckData = 1; break;
491            default: return 1;
492        }
493    }
494
495    if( showHelp || showVersion )
496        return 0;
497
498    if( optind >= argc )
499        return 1;
500
501    torrentPath = argv[optind];
502    return 0;
503}
504
505static void sigHandler( int signal )
506{
507    switch( signal )
508    {
509        case SIGINT:
510            gotsig = 1;
511            break;
512           
513        case SIGHUP:
514            manualUpdate = 1;
515            break;
516
517        default:
518            break;
519    }
520}
Note: See TracBrowser for help on using the repository browser.