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

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

(1.2x) backport cli documentation fix from #979

  • Property svn:keywords set to Date Rev Author Id
File size: 15.2 KB
Line 
1/******************************************************************************
2 * $Id: transmissioncli.c 6177 2008-06-13 19:30:51Z 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"  -u, --upload <int>        Maximum upload rate (-1 = no limit, default = 20)\n"
63"  -v, --verbose <int>       Verbose level (0 to 2, default = 0)\n"
64"  -y, --recheck             Force a recheck of the torrent data\n";
65
66static int           showHelp      = 0;
67static int           showInfo      = 0;
68static int           showScrape    = 0;
69static int           showVersion   = 0;
70static int           isPrivate     = 0;
71static int           verboseLevel  = 0;
72static int           bindPort      = TR_DEFAULT_PORT;
73static int           uploadLimit   = 20;
74static int           downloadLimit = -1;
75static char        * torrentPath   = NULL;
76static char        * savePath      = ".";
77static int           natTraversal  = 0;
78static int           recheckData   = 0;
79static sig_atomic_t  gotsig        = 0;
80static sig_atomic_t  manualUpdate  = 0;
81
82static char          * finishCall   = NULL;
83static char          * announce     = NULL;
84static char          * configdir    = NULL;
85static char          * sourceFile   = NULL;
86static char          * comment      = NULL;
87
88static int  parseCommandLine ( int argc, char ** argv );
89static void sigHandler       ( int signal );
90
91static char *
92getStringRatio( float ratio )
93{
94    static char string[20];
95
96    if( ratio == TR_RATIO_NA )
97        return "n/a";
98    snprintf( string, sizeof string, "%.3f", ratio );
99    return string;
100}
101
102#define LINEWIDTH 80
103
104static void
105torrentStateChanged( tr_torrent   * torrent UNUSED,
106                     cp_status_t    status UNUSED,
107                     void         * user_data UNUSED )
108{
109    system( finishCall );
110}
111
112int
113main( int argc, char ** argv )
114{
115    int i, error;
116    tr_handle  * h;
117    tr_ctor * ctor;
118    tr_torrent * tor = NULL;
119
120    printf( "Transmission %s - http://www.transmissionbt.com/\n",
121            LONG_VERSION_STRING );
122
123    /* Get options */
124    if( parseCommandLine( argc, argv ) )
125    {
126        printf( USAGE, argv[0], TR_DEFAULT_PORT );
127        return EXIT_FAILURE;
128    }
129
130    if( showVersion )
131        return EXIT_SUCCESS;
132
133    if( showHelp )
134    {
135        printf( USAGE, argv[0], TR_DEFAULT_PORT );
136        return EXIT_SUCCESS;
137    }
138
139    if( bindPort < 1 || bindPort > 65535 )
140    {
141        printf( "Invalid port '%d'\n", bindPort );
142        return EXIT_FAILURE;
143    }
144
145    /* don't bind the port if we're just running the CLI
146     * to get metainfo or to create a torrent */
147    if( showInfo || showScrape || ( sourceFile != NULL ) )
148        bindPort = -1;
149
150    if( configdir == NULL )
151        configdir = strdup( tr_getDefaultConfigDir( ) );
152
153    /* Initialize libtransmission */
154    h = tr_initFull( configdir,
155                     "cli",                         /* tag */
156                     1,                             /* pex enabled */
157                     natTraversal,                  /* nat enabled */
158                     bindPort,                      /* public port */
159                     TR_ENCRYPTION_PREFERRED,       /* encryption mode */
160                     uploadLimit >= 0,              /* use upload speed limit? */
161                     uploadLimit,                   /* upload speed limit */
162                     downloadLimit >= 0,            /* use download speed limit? */
163                     downloadLimit,                 /* download speed limit */
164                     TR_DEFAULT_GLOBAL_PEER_LIMIT,
165                     verboseLevel + 1,              /* messageLevel */
166                     0,                             /* is message queueing enabled? */
167                     0,                             /* use the blocklist? */
168                     TR_DEFAULT_PEER_SOCKET_TOS );
169
170    if( sourceFile && *sourceFile ) /* creating a torrent */
171    {
172        int err;
173        tr_metainfo_builder * builder = tr_metaInfoBuilderCreate( h, sourceFile );
174        tr_makeMetaInfo( builder, torrentPath, announce, comment, isPrivate );
175        while( !builder->isDone ) {
176            tr_wait( 1000 );
177            printf( "." );
178        }
179        err = builder->result;
180        tr_metaInfoBuilderFree( builder );
181        return err;
182    }
183
184    ctor = tr_ctorNew( h );
185    tr_ctorSetMetainfoFromFile( ctor, torrentPath );
186    tr_ctorSetPaused( ctor, TR_FORCE, 0 );
187    tr_ctorSetDestination( ctor, TR_FORCE, savePath );
188
189    if( showInfo )
190    {
191        tr_info info;
192
193        if( !tr_torrentParse( h, ctor, &info ) )
194        {
195            int prevTier = -1;
196            tr_file_index_t ff;
197
198            printf( "hash:\t" );
199            for( i=0; i<SHA_DIGEST_LENGTH; ++i )
200                printf( "%02x", info.hash[i] );
201            printf( "\n" );
202
203            printf( "name:\t%s\n", info.name );
204
205            for( i=0; i<info.trackerCount; ++i ) {
206                if( prevTier != info.trackers[i].tier ) {
207                    prevTier = info.trackers[i].tier;
208                    printf( "\ntracker tier #%d:\n", (prevTier+1) );
209                }
210                printf( "\tannounce:\t%s\n", info.trackers[i].announce );
211            }
212
213            printf( "size:\t%"PRIu64" (%"PRIu64" * %d + %"PRIu64")\n",
214                    info.totalSize, info.totalSize / info.pieceSize,
215                    info.pieceSize, info.totalSize % info.pieceSize );
216
217            if( info.comment[0] )
218                printf( "comment:\t%s\n", info.comment );
219            if( info.creator[0] )
220                printf( "creator:\t%s\n", info.creator );
221            if( info.isPrivate )
222                printf( "private flag set\n" );
223
224            printf( "file(s):\n" );
225            for( ff=0; ff<info.fileCount; ++ff )
226                printf( "\t%s (%"PRIu64")\n", info.files[ff].name, info.files[ff].length );
227
228            tr_metainfoFree( &info );
229        }
230
231        tr_ctorFree( ctor );
232        goto cleanup;
233    }
234
235
236    tor = tr_torrentNew( h, ctor, &error );
237    tr_ctorFree( ctor );
238    if( tor == NULL )
239    {
240        printf( "Failed opening torrent file `%s'\n", torrentPath );
241        tr_close( h );
242        return EXIT_FAILURE;
243    }
244
245    if( showScrape )
246    {
247        const struct tr_stat * stats;
248        const uint64_t start = tr_date( );
249        printf( "Scraping, Please wait...\n" );
250       
251        do
252        {
253            stats = tr_torrentStat( tor );
254            if( !stats || tr_date() - start > 20000 )
255            {
256                printf( "Scrape failed.\n" );
257                goto cleanup;
258            }
259            tr_wait( 2000 );
260        }
261        while( stats->completedFromTracker == -1 || stats->leechers == -1 || stats->seeders == -1 );
262       
263        printf( "%d seeder(s), %d leecher(s), %d download(s).\n",
264            stats->seeders, stats->leechers, stats->completedFromTracker );
265
266        goto cleanup;
267    }
268
269    signal( SIGINT, sigHandler );
270    signal( SIGHUP, sigHandler );
271
272    tr_torrentSetStatusCallback( tor, torrentStateChanged, NULL );
273    tr_torrentStart( tor );
274
275    for( ;; )
276    {
277        char string[LINEWIDTH];
278        int  chars = 0;
279        const struct tr_stat * s;
280
281        tr_wait( 1000 );
282
283        if( gotsig )
284        {
285            gotsig = 0;
286            tr_torrentStop( tor );
287            tr_natTraversalEnable( h, 0 );
288        }
289       
290        if( manualUpdate )
291        {
292            manualUpdate = 0;
293            if ( !tr_torrentCanManualUpdate( tor ) )
294                fprintf( stderr, "\rReceived SIGHUP, but can't send a manual update now\n" );
295            else {
296                fprintf( stderr, "\rReceived SIGHUP: manual update scheduled\n" );
297                tr_manualUpdate( tor );
298            }
299        }
300       
301        if( recheckData )
302        {
303            recheckData = 0;
304            tr_torrentVerify( tor );
305        }
306
307        s = tr_torrentStat( tor );
308
309        if( s->status & TR_STATUS_CHECK_WAIT )
310        {
311            chars = snprintf( string, sizeof string,
312                "Waiting to verify local files..." );
313        }
314        else if( s->status & TR_STATUS_CHECK )
315        {
316            chars = snprintf( string, sizeof string,
317                "Verifying local files... %.2f%%, found %.2f%% valid", 100 * s->recheckProgress, 100.0 * s->percentDone );
318        }
319        else if( s->status & TR_STATUS_DOWNLOAD )
320        {
321            chars = snprintf( string, sizeof string,
322                "Progress: %.2f %%, %d peer%s, dl from %d (%.2f KB/s), "
323                "ul to %d (%.2f KB/s) [%s]", 100.0 * s->percentDone,
324                s->peersConnected, ( s->peersConnected == 1 ) ? "" : "s",
325                s->peersSendingToUs, s->rateDownload,
326                s->peersGettingFromUs, s->rateUpload,
327                getStringRatio(s->ratio) );
328        }
329        else if( s->status & TR_STATUS_SEED )
330        {
331            chars = snprintf( string, sizeof string,
332                "Seeding, uploading to %d of %d peer(s), %.2f KB/s [%s]",
333                s->peersGettingFromUs, s->peersConnected,
334                s->rateUpload, getStringRatio(s->ratio) );
335        }
336        else if( s->status & TR_STATUS_STOPPED )
337        {
338            break;
339        }
340        if( ( signed )sizeof string > chars )
341        {
342            memset( &string[chars], ' ', sizeof string - 1 - chars );
343        }
344        string[sizeof string - 1] = '\0';
345        fprintf( stderr, "\r%s", string );
346
347        if( s->error )
348        {
349            fprintf( stderr, "\n%s\n", s->errorString );
350        }
351        else if( verboseLevel > 0 )
352        {
353            fprintf( stderr, "\n" );
354        }
355    }
356    fprintf( stderr, "\n" );
357
358    /* Try for 5 seconds to delete any port mappings for nat traversal */
359    tr_natTraversalEnable( h, 0 );
360    for( i = 0; i < 10; i++ )
361    {
362        const tr_handle_status * hstat = tr_handleStatus( h );
363        if( TR_NAT_TRAVERSAL_UNMAPPED == hstat->natTraversalStatus )
364        {
365            /* Port mappings were deleted */
366            break;
367        }
368        tr_wait( 500 );
369    }
370   
371cleanup:
372    tr_torrentClose( tor );
373    tr_close( h );
374
375    return EXIT_SUCCESS;
376}
377
378static int
379parseCommandLine( int argc, char ** argv )
380{
381    for( ;; )
382    {
383        static const struct option long_options[] = {
384            { "announce",      required_argument, NULL, 'a' },
385            { "create-from",   required_argument, NULL, 'c' },
386            { "download",      required_argument, NULL, 'd' },
387            { "finish",        required_argument, NULL, 'f' },
388            { "config-dir",    required_argument, NULL, 'g' },
389            { "help",          no_argument,       NULL, 'h' },
390            { "info",          no_argument,       NULL, 'i' },
391            { "comment",       required_argument, NULL, 'm' },
392            { "nat-traversal", no_argument,       NULL, 'n' },
393            { "output-dir",    required_argument, NULL, 'o' },
394            { "port",          required_argument, NULL, 'p' },
395            { "private",       no_argument,       NULL, 'r' },
396            { "scrape",        no_argument,       NULL, 's' },
397            { "upload",        required_argument, NULL, 'u' },
398            { "verbose",       required_argument, NULL, 'v' },
399            { "version",       no_argument,       NULL, 'V' },
400            { "recheck",       no_argument,       NULL, 'y' },
401            { 0, 0, 0, 0} };
402        int optind = 0;
403        int c = getopt_long( argc, argv,
404                             "a:c:d:f:g:him:no:p:rsu:v:Vy",
405                             long_options, &optind );
406        if( c < 0 )
407        {
408            break;
409        }
410        switch( c )
411        {
412            case 'a': announce = optarg; break;
413            case 'c': sourceFile = optarg; break;
414            case 'd': downloadLimit = atoi( optarg ); break;
415            case 'f': finishCall = optarg; break;
416            case 'g': configdir = strdup( optarg ); break;
417            case 'h': showHelp = 1; break;
418            case 'i': showInfo = 1; break;
419            case 'm': comment = optarg; break;
420            case 'n': natTraversal = 1; break;
421            case 'o': savePath = optarg;
422            case 'p': bindPort = atoi( optarg ); break;
423            case 'r': isPrivate = 1; break;
424            case 's': showScrape = 1; break;
425            case 'u': uploadLimit = atoi( optarg ); break;
426            case 'v': verboseLevel = atoi( optarg ); break;
427            case 'V': showVersion = 1; break;
428            case 'y': recheckData = 1; break;
429            default: return 1;
430        }
431    }
432
433    if( showHelp || showVersion )
434        return 0;
435
436    if( optind >= argc )
437        return 1;
438
439    torrentPath = argv[optind];
440    return 0;
441}
442
443static void sigHandler( int signal )
444{
445    switch( signal )
446    {
447        case SIGINT:
448            gotsig = 1;
449            break;
450           
451        case SIGHUP:
452            manualUpdate = 1;
453            break;
454
455        default:
456            break;
457    }
458}
Note: See TracBrowser for help on using the repository browser.