source: trunk/cli/transmissioncli.c @ 6120

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

wire up the backend proxy support.

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