source: trunk/cli/cli.c @ 10640

Last change on this file since 10640 was 10640, checked in by charles, 12 years ago

(trunk) #1796 "run script after torrent completion" -- (1) add to transmission-remote. (2) remove cli's custom script handler and have it use libtransmission instead.

  • Property svn:keywords set to Date Rev Author Id
File size: 19.8 KB
Line 
1/******************************************************************************
2 * $Id: cli.c 10640 2010-05-11 13:36:21Z 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/tr-getopt.h>
35#include <libtransmission/utils.h> /* tr_wait_msec */
36#include <libtransmission/version.h>
37#include <libtransmission/web.h> /* tr_webRun */
38
39#define LINEWIDTH 80
40#define MY_NAME "transmissioncli"
41
42static tr_bool showInfo         = 0;
43static tr_bool showScrape       = 0;
44static tr_bool isPrivate        = 0;
45static tr_bool verify           = 0;
46static sig_atomic_t gotsig           = 0;
47static sig_atomic_t manualUpdate     = 0;
48
49static const char * torrentPath  = NULL;
50static const char * sourceFile   = NULL;
51static const char * comment      = NULL;
52
53#define MAX_ANNOUNCE 128
54static tr_tracker_info announce[MAX_ANNOUNCE];
55static int announceCount = 0;
56
57static const struct tr_option options[] =
58{
59    { 'a', "announce",             "Set the new torrent's announce URL", "a",  1, "<url>"     },
60    { 'b', "blocklist",            "Enable peer blocklists", "b",  0, NULL        },
61    { 'B', "no-blocklist",         "Disable peer blocklists", "B",  0, NULL        },
62    { 'c', "comment",              "Set the new torrent's comment", "c",  1, "<comment>" },
63    { 'd', "downlimit",            "Set max download speed in KiB/s", "d",  1, "<speed>"   },
64    { 'D', "no-downlimit",         "Don't limit the download speed", "D",  0, NULL        },
65    { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL        },
66    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL        },
67    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL        },
68    { 'f', "finish",               "Run a script when the torrent finishes", "f", 1, "<script>" },
69    { 'g', "config-dir",           "Where to find configuration files", "g", 1, "<path>" },
70    { 'i', "info",                 "Show torrent details and exit", "i",  0, NULL        },
71    { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP", "m",  0, NULL        },
72    { 'M', "no-portmap",           "Disable portmapping", "M",  0, NULL        },
73    { 'n', "new",                  "Create a new torrent", "n", 1, "<source>" },
74    { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
75    { 'r', "private",              "Set the new torrent's 'private' flag", "r",  0, NULL        },
76    { 's', "scrape",               "Scrape the torrent and exit", "s",  0, NULL        },
77    { 't', "tos", "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR ")", "t", 1, "<tos>" },
78    { 'u', "uplimit",              "Set max upload speed in KiB/s", "u",  1, "<speed>"   },
79    { 'U', "no-uplimit",           "Don't limit the upload speed", "U",  0, NULL        },
80    { 'v', "verify",               "Verify the specified torrent", "v",  0, NULL        },
81    { 'w', "download-dir",         "Where to save downloaded data", "w",  1, "<path>"    },
82    { 0, NULL, NULL, NULL, 0, NULL }
83};
84
85static const char *
86getUsage( void )
87{
88    return "A fast and easy BitTorrent client\n"
89           "\n"
90           "Usage: " MY_NAME " [options] <file|url|magnet>";
91}
92
93static int parseCommandLine( tr_benc*, int argc, const char ** argv );
94
95static void         sigHandler( int signal );
96
97static char*
98tr_strlratio( char * buf,
99              double ratio,
100              size_t buflen )
101{
102    if( (int)ratio == TR_RATIO_NA )
103        tr_strlcpy( buf, _( "None" ), buflen );
104    else if( (int)ratio == TR_RATIO_INF )
105        tr_strlcpy( buf, "Inf", buflen );
106    else if( ratio < 10.0 )
107        tr_snprintf( buf, buflen, "%.2f", ratio );
108    else if( ratio < 100.0 )
109        tr_snprintf( buf, buflen, "%.1f", ratio );
110    else
111        tr_snprintf( buf, buflen, "%.0f", ratio );
112    return buf;
113}
114
115static int
116is_rfc2396_alnum( char ch )
117{
118    return ( '0' <= ch && ch <= '9' )
119           || ( 'A' <= ch && ch <= 'Z' )
120           || ( 'a' <= ch && ch <= 'z' );
121}
122
123static void
124escape( char *          out,
125        const uint8_t * in,
126        int             in_len )                     /* rfc2396 */
127{
128    const uint8_t *end = in + in_len;
129
130    while( in != end )
131        if( is_rfc2396_alnum( *in ) )
132            *out++ = (char) *in++;
133        else
134            out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
135
136    *out = '\0';
137}
138
139static tr_bool waitingOnWeb;
140
141static void
142onTorrentFileDownloaded( tr_session   * session UNUSED,
143                         long           response_code UNUSED,
144                         const void   * response,
145                         size_t         response_byte_count,
146                         void         * ctor )
147{
148    tr_ctorSetMetainfo( ctor, response, response_byte_count );
149    waitingOnWeb = FALSE;
150}
151
152static int leftToScrape = 0;
153
154static void
155scrapeDoneFunc( tr_session   * session UNUSED,
156                long           response_code,
157                const void   * response,
158                size_t         response_byte_count,
159                void         * host )
160{
161    tr_benc top, *files;
162
163    if( !tr_bencLoad( response, response_byte_count, &top, NULL )
164      && tr_bencDictFindDict( &top, "files", &files )
165      && files->val.l.count >= 2 )
166    {
167        int64_t   complete = -1, incomplete = -1, downloaded = -1;
168        tr_benc * hash = &files->val.l.vals[1];
169        tr_bencDictFindInt( hash, "complete", &complete );
170        tr_bencDictFindInt( hash, "incomplete", &incomplete );
171        tr_bencDictFindInt( hash, "downloaded", &downloaded );
172        printf( "%4d seeders, %4d leechers, %5d downloads at %s\n",
173                (int)complete, (int)incomplete, (int)downloaded,
174                (char*)host );
175        tr_bencFree( &top );
176    }
177    else
178        fprintf( stderr, "Unable to parse response (http code %lu) at %s",
179                 response_code,
180                 (char*)host );
181
182    --leftToScrape;
183
184    tr_free( host );
185}
186
187static void
188dumpInfo( FILE *          out,
189          const tr_info * inf )
190{
191    int             i;
192    int             prevTier = -1;
193    tr_file_index_t ff;
194
195    fprintf( out, "hash:\t" );
196    for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
197        fprintf( out, "%02x", inf->hash[i] );
198    fprintf( out, "\n" );
199
200    fprintf( out, "name:\t%s\n", inf->name );
201
202    for( i = 0; i < inf->trackerCount; ++i )
203    {
204        if( prevTier != inf->trackers[i].tier )
205        {
206            prevTier = inf->trackers[i].tier;
207            fprintf( out, "\ntracker tier #%d:\n", ( prevTier + 1 ) );
208        }
209        fprintf( out, "\tannounce:\t%s\n", inf->trackers[i].announce );
210    }
211
212    fprintf( out, "size:\t%" PRIu64 " (%" PRIu64 " * %d + %" PRIu64 ")\n",
213             inf->totalSize, inf->totalSize / inf->pieceSize,
214             inf->pieceSize, inf->totalSize % inf->pieceSize );
215
216    if( inf->comment && *inf->comment )
217        fprintf( out, "comment:\t%s\n", inf->comment );
218    if( inf->creator && *inf->creator )
219        fprintf( out, "creator:\t%s\n", inf->creator );
220    if( inf->isPrivate )
221        fprintf( out, "private flag set\n" );
222
223    fprintf( out, "file(s):\n" );
224    for( ff = 0; ff < inf->fileCount; ++ff )
225        fprintf( out, "\t%s (%" PRIu64 ")\n", inf->files[ff].name,
226                 inf->files[ff].length );
227}
228
229static void
230getStatusStr( const tr_stat * st,
231              char *          buf,
232              size_t          buflen )
233{
234    if( st->activity & TR_STATUS_CHECK_WAIT )
235    {
236        tr_snprintf( buf, buflen, "Waiting to verify local files" );
237    }
238    else if( st->activity & TR_STATUS_CHECK )
239    {
240        tr_snprintf( buf, buflen,
241                     "Verifying local files (%.2f%%, %.2f%% valid)",
242                     tr_truncd( 100 * st->recheckProgress, 2 ),
243                     tr_truncd( 100 * st->percentDone, 2 ) );
244    }
245    else if( st->activity & TR_STATUS_DOWNLOAD )
246    {
247        char ratioStr[80];
248        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
249        tr_snprintf(
250            buf, buflen,
251            "Progress: %.1f%%, dl from %d of %d peers (%.0f KiB/s), "
252            "ul to %d (%.0f KiB/s) [%s]",
253            tr_truncd( 100 * st->percentDone, 1 ),
254            st->peersSendingToUs,
255            st->peersConnected,
256            st->pieceDownloadSpeed,
257            st->peersGettingFromUs,
258            st->pieceUploadSpeed,
259            ratioStr );
260    }
261    else if( st->activity & TR_STATUS_SEED )
262    {
263        char ratioStr[80];
264        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
265        tr_snprintf(
266            buf, buflen,
267            "Seeding, uploading to %d of %d peer(s), %.0f KiB/s [%s]",
268            st->peersGettingFromUs, st->peersConnected,
269            st->pieceUploadSpeed, ratioStr );
270    }
271    else *buf = '\0';
272}
273
274static const char*
275getConfigDir( int argc, const char ** argv )
276{
277    int c;
278    const char * configDir = NULL;
279    const char * optarg;
280    const int ind = tr_optind;
281
282    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ))) {
283        if( c == 'g' ) {
284            configDir = optarg;
285            break;
286        }
287    }
288
289    tr_optind = ind;
290
291    if( configDir == NULL )
292        configDir = tr_getDefaultConfigDir( MY_NAME );
293
294    return configDir;
295}
296
297int
298main( int     argc,
299      char ** argv )
300{
301    int           error;
302    tr_session  * h;
303    tr_ctor     * ctor;
304    tr_torrent  * tor = NULL;
305    tr_benc       settings;
306    const char  * configDir;
307    tr_bool       haveSource;
308    tr_bool       haveAnnounce;
309    uint8_t     * fileContents;
310    size_t        fileLength;
311
312    printf( "Transmission %s - http://www.transmissionbt.com/\n",
313            LONG_VERSION_STRING );
314
315    /* user needs to pass in at least one argument */
316    if( argc < 2 ) {
317        tr_getopt_usage( MY_NAME, getUsage( ), options );
318        return EXIT_FAILURE;
319    }
320
321    /* load the defaults from config file + libtransmission defaults */
322    tr_bencInitDict( &settings, 0 );
323    configDir = getConfigDir( argc, (const char**)argv );
324    tr_sessionLoadSettings( &settings, configDir, MY_NAME );
325
326    /* the command line overrides defaults */
327    if( parseCommandLine( &settings, argc, (const char**)argv ) )
328        return EXIT_FAILURE;
329
330    /* Check the options for validity */
331    if( !torrentPath ) {
332        fprintf( stderr, "No torrent specified!\n" );
333        return EXIT_FAILURE;
334    }
335
336    /* don't bind the port if we're just running the CLI
337       to get metainfo or to create a torrent */
338    if( showInfo || showScrape || ( sourceFile != NULL ) )
339        tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_PORT, -1 );
340
341    h = tr_sessionInit( "cli", configDir, FALSE, &settings );
342
343    haveSource = sourceFile && *sourceFile;
344    haveAnnounce = announceCount > 0;
345
346    if( haveSource && !haveAnnounce )
347        fprintf( stderr, "Did you mean to create a torrent without a tracker's announce URL?\n" );
348
349    if( haveSource ) /* creating a torrent */
350    {
351        int err;
352        tr_metainfo_builder * b;
353        fprintf( stderr, "creating torrent \"%s\"\n", torrentPath );
354
355        b = tr_metaInfoBuilderCreate( sourceFile );
356        tr_makeMetaInfo( b, torrentPath, announce, announceCount, comment, isPrivate );
357        while( !b->isDone )
358        {
359            tr_wait_msec( 1000 );
360            printf( "." );
361        }
362
363        err = b->result;
364        tr_metaInfoBuilderFree( b );
365        return err;
366    }
367
368    ctor = tr_ctorNew( h );
369
370    fileContents = tr_loadFile( torrentPath, &fileLength );
371    tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
372    if( fileContents != NULL ) {
373        tr_ctorSetMetainfo( ctor, fileContents, fileLength );
374    } else if( !memcmp( torrentPath, "magnet:?", 8 ) ) {
375        tr_ctorSetMetainfoFromMagnetLink( ctor, torrentPath );
376    } else if( !memcmp( torrentPath, "http", 4 ) ) {
377        tr_webRun( h, torrentPath, NULL, onTorrentFileDownloaded, ctor );
378        waitingOnWeb = TRUE;
379        while( waitingOnWeb ) tr_wait_msec( 1000 );
380    }
381    tr_free( fileContents );
382
383    if( showScrape )
384    {
385        tr_info info;
386
387        if( !tr_torrentParse( ctor, &info ) )
388        {
389            int          i;
390            const time_t start = time( NULL );
391            for( i = 0; i < info.trackerCount; ++i )
392            {
393                if( info.trackers[i].scrape )
394                {
395                    const char * scrape = info.trackers[i].scrape;
396                    char         escaped[SHA_DIGEST_LENGTH * 3 + 1];
397                    char *       url, *host;
398                    escape( escaped, info.hash, SHA_DIGEST_LENGTH );
399                    url = tr_strdup_printf( "%s%cinfo_hash=%s",
400                                            scrape,
401                                            strchr( scrape,
402                                                    '?' ) ? '&' : '?',
403                                            escaped );
404                    tr_urlParse( scrape, -1, NULL, &host, NULL, NULL );
405                    ++leftToScrape;
406
407                    tr_webRun( h, url, NULL, scrapeDoneFunc, host );
408                    tr_free( url );
409                }
410            }
411
412            fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
413
414            while( leftToScrape > 0 && ( ( time( NULL ) - start ) < 20 ) )
415                tr_wait_msec( 250 );
416        }
417        goto cleanup;
418    }
419
420    if( showInfo )
421    {
422        tr_info info;
423
424        if( !tr_torrentParse( ctor, &info ) )
425        {
426            dumpInfo( stdout, &info );
427            tr_metainfoFree( &info );
428        }
429
430        tr_ctorFree( ctor );
431        goto cleanup;
432    }
433
434    tor = tr_torrentNew( ctor, &error );
435    tr_ctorFree( ctor );
436    if( !tor )
437    {
438        fprintf( stderr, "Failed opening torrent file `%s'\n", torrentPath );
439        tr_sessionClose( h );
440        return EXIT_FAILURE;
441    }
442
443    signal( SIGINT, sigHandler );
444#ifndef WIN32
445    signal( SIGHUP, sigHandler );
446#endif
447    tr_torrentStart( tor );
448
449    if( verify )
450    {
451        verify = 0;
452        tr_torrentVerify( tor );
453    }
454
455    for( ; ; )
456    {
457        char            line[LINEWIDTH];
458        const tr_stat * st;
459        const char * messageName[] = { NULL, "Tracker gave a warning:",
460                                             "Tracker gave an error:",
461                                             "Error:" };
462
463        tr_wait_msec( 200 );
464
465        if( gotsig )
466        {
467            gotsig = 0;
468            printf( "\nStopping torrent...\n" );
469            tr_torrentStop( tor );
470        }
471
472        if( manualUpdate )
473        {
474            manualUpdate = 0;
475            if( !tr_torrentCanManualUpdate( tor ) )
476                fprintf(
477                    stderr,
478                    "\nReceived SIGHUP, but can't send a manual update now\n" );
479            else
480            {
481                fprintf( stderr,
482                         "\nReceived SIGHUP: manual update scheduled\n" );
483                tr_torrentManualUpdate( tor );
484            }
485        }
486
487        st = tr_torrentStat( tor );
488        if( st->activity & TR_STATUS_STOPPED )
489            break;
490
491        getStatusStr( st, line, sizeof( line ) );
492        printf( "\r%-*s", LINEWIDTH, line );
493
494        if( messageName[st->error] )
495            fprintf( stderr, "\n%s: %s\n", messageName[st->error], st->errorString );
496    }
497
498cleanup:
499
500    tr_sessionSaveSettings( h, configDir, &settings );
501
502    printf( "\n" );
503    tr_bencFree( &settings );
504    tr_sessionClose( h );
505    return EXIT_SUCCESS;
506}
507
508/***
509****
510****
511****
512***/
513
514static int
515parseCommandLine( tr_benc * d, int argc, const char ** argv )
516{
517    int          c;
518    const char * optarg;
519
520    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg )))
521    {
522        switch( c )
523        {
524            case 'a': if( announceCount + 1 < MAX_ANNOUNCE ) {
525                          announce[announceCount].tier = announceCount;
526                          announce[announceCount].announce = (char*) optarg;
527                          ++announceCount;
528                      }
529                      break;
530            case 'b': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, TRUE );
531                      break;
532            case 'B': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
533                      break;
534            case 'c': comment = optarg;
535                      break;
536            case 'd': tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED, atoi( optarg ) );
537                      tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
538                      break;
539            case 'D': tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
540                      break;
541            case 'f': tr_bencDictAddStr( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, optarg );
542                      tr_bencDictAddBool( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, TRUE );
543                      break;
544            case 'g': /* handled above */
545                      break;
546            case 'i': showInfo = 1;
547                      break;
548            case 'm': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
549                      break;
550            case 'M': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
551                      break;
552            case 'n': sourceFile = optarg; break;
553            case 'p': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
554                      break;
555            case 'r': isPrivate = 1;
556                      break;
557            case 's': showScrape = 1;
558                      break;
559            case 't': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_SOCKET_TOS, atoi( optarg ) );
560                      break;
561            case 'u': tr_bencDictAddInt( d, TR_PREFS_KEY_USPEED, atoi( optarg ) );
562                      tr_bencDictAddBool( d, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
563                      break;
564            case 'U': tr_bencDictAddBool( d, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
565                      break;
566            case 'v': verify = 1;
567                      break;
568            case 'w': tr_bencDictAddStr( d, TR_PREFS_KEY_DOWNLOAD_DIR, optarg );
569                      break;
570            case 910: tr_bencDictAddInt( d, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_REQUIRED );
571                      break;
572            case 911: tr_bencDictAddInt( d, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
573                      break;
574            case 912: tr_bencDictAddInt( d, TR_PREFS_KEY_ENCRYPTION, TR_CLEAR_PREFERRED );
575                      break;
576            case TR_OPT_UNK:
577                      torrentPath = optarg;
578                      break;
579            default: return 1;
580        }
581    }
582
583    return 0;
584}
585
586static void
587sigHandler( int signal )
588{
589    switch( signal )
590    {
591        case SIGINT:
592            gotsig = 1; break;
593
594#ifndef WIN32
595        case SIGHUP:
596            manualUpdate = 1; break;
597
598#endif
599        default:
600            break;
601    }
602}
603
Note: See TracBrowser for help on using the repository browser.