source: trunk/cli/transmissioncli.c @ 6300

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

steal some blocklist/encryption options from remote & reuse them in cli

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