source: trunk/cli/transmissioncli.c @ 6156

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

(cli) #938 - transmissioncli won't scrape

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