source: trunk/cli/cli.c @ 11896

Last change on this file since 11896 was 11896, checked in by jordan, 11 years ago

(trunk) #4032 "Better error detection / reporting in http announces" -- added to trunk.

This patch adds two new flags to the callback function -- did_connect and did_timeout -- that are calculated inside of web.c using information from libcurl. This allows the announcer to detect timeouts more accurately and also to distinguish between unresponsive peers (which get the preexisting "Tracker did not respond" error message) and unconnectable peers (which get a new error message, "Could not connect to tracker").

  • Property svn:keywords set to Date Rev Author Id
File size: 14.5 KB
Line 
1/******************************************************************************
2 * $Id: cli.c 11896 2011-02-17 02:26:24Z jordan $
3 *
4 * Copyright (c) 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/tr-getopt.h>
34#include <libtransmission/utils.h> /* tr_wait_msec */
35#include <libtransmission/version.h>
36#include <libtransmission/web.h> /* tr_webRun */
37
38/***
39****
40***/
41
42#define MEM_K 1024
43#define MEM_K_STR "KiB"
44#define MEM_M_STR "MiB"
45#define MEM_G_STR "GiB"
46#define MEM_T_STR "TiB"
47
48#define DISK_K 1024
49#define DISK_B_STR   "B"
50#define DISK_K_STR "KiB"
51#define DISK_M_STR "MiB"
52#define DISK_G_STR "GiB"
53#define DISK_T_STR "TiB"
54
55#define SPEED_K 1024
56#define SPEED_B_STR   "B/s"
57#define SPEED_K_STR "KiB/s"
58#define SPEED_M_STR "MiB/s"
59#define SPEED_G_STR "GiB/s"
60#define SPEED_T_STR "TiB/s"
61
62/***
63****
64***/
65
66#define LINEWIDTH 80
67#define MY_CONFIG_NAME "transmission"
68#define MY_READABLE_NAME "transmission-cli"
69
70static tr_bool showVersion = FALSE;
71static tr_bool verify                = 0;
72static sig_atomic_t gotsig           = 0;
73static sig_atomic_t manualUpdate     = 0;
74
75static const char * torrentPath  = NULL;
76
77static const struct tr_option options[] =
78{
79    { 'b', "blocklist",            "Enable peer blocklists", "b",  0, NULL },
80    { 'B', "no-blocklist",         "Disable peer blocklists", "B",  0, NULL },
81    { 'd', "downlimit",            "Set max download speed in "SPEED_K_STR, "d",  1, "<speed>" },
82    { 'D', "no-downlimit",         "Don't limit the download speed", "D",  0, NULL },
83    { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL },
84    { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
85    { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
86    { 'f', "finish",               "Run a script when the torrent finishes", "f", 1, "<script>" },
87    { 'g', "config-dir",           "Where to find configuration files", "g", 1, "<path>" },
88    { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP", "m",  0, NULL },
89    { 'M', "no-portmap",           "Disable portmapping", "M",  0, NULL },
90    { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
91    { 't', "tos", "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR ")", "t", 1, "<tos>" },
92    { 'u', "uplimit",              "Set max upload speed in "SPEED_K_STR, "u",  1, "<speed>"   },
93    { 'U', "no-uplimit",           "Don't limit the upload speed", "U",  0, NULL        },
94    { 'v', "verify",               "Verify the specified torrent", "v",  0, NULL        },
95    { 'V', "version",              "Show version number and exit", "V", 0, NULL },
96    { 'w', "download-dir",         "Where to save downloaded data", "w",  1, "<path>"    },
97    { 0, NULL, NULL, NULL, 0, NULL }
98};
99
100static const char *
101getUsage( void )
102{
103    return "A fast and easy BitTorrent client\n"
104           "\n"
105           "Usage: " MY_READABLE_NAME " [options] <file|url|magnet>";
106}
107
108static int parseCommandLine( tr_benc*, int argc, const char ** argv );
109
110static void         sigHandler( int signal );
111
112static char*
113tr_strlratio( char * buf,
114              double ratio,
115              size_t buflen )
116{
117    if( (int)ratio == TR_RATIO_NA )
118        tr_strlcpy( buf, _( "None" ), buflen );
119    else if( (int)ratio == TR_RATIO_INF )
120        tr_strlcpy( buf, "Inf", buflen );
121    else if( ratio < 10.0 )
122        tr_snprintf( buf, buflen, "%.2f", ratio );
123    else if( ratio < 100.0 )
124        tr_snprintf( buf, buflen, "%.1f", ratio );
125    else
126        tr_snprintf( buf, buflen, "%.0f", ratio );
127    return buf;
128}
129
130static tr_bool waitingOnWeb;
131
132static void
133onTorrentFileDownloaded( tr_session   * session UNUSED,
134                         tr_bool        did_connect UNUSED,
135                         tr_bool        did_timeout UNUSED,
136                         long           response_code UNUSED,
137                         const void   * response,
138                         size_t         response_byte_count,
139                         void         * ctor )
140{
141    tr_ctorSetMetainfo( ctor, response, response_byte_count );
142    waitingOnWeb = FALSE;
143}
144
145static void
146getStatusStr( const tr_stat * st,
147              char *          buf,
148              size_t          buflen )
149{
150    if( st->activity & TR_STATUS_CHECK_WAIT )
151    {
152        tr_snprintf( buf, buflen, "Waiting to verify local files" );
153    }
154    else if( st->activity & TR_STATUS_CHECK )
155    {
156        tr_snprintf( buf, buflen,
157                     "Verifying local files (%.2f%%, %.2f%% valid)",
158                     tr_truncd( 100 * st->recheckProgress, 2 ),
159                     tr_truncd( 100 * st->percentDone, 2 ) );
160    }
161    else if( st->activity & TR_STATUS_DOWNLOAD )
162    {
163        char upStr[80];
164        char dnStr[80];
165        char ratioStr[80];
166
167        tr_formatter_speed_KBps( upStr, st->pieceUploadSpeed_KBps, sizeof( upStr ) );
168        tr_formatter_speed_KBps( dnStr, st->pieceDownloadSpeed_KBps, sizeof( dnStr ) );
169        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
170
171        tr_snprintf( buf, buflen,
172            "Progress: %.1f%%, "
173            "dl from %d of %d peers (%s), "
174            "ul to %d (%s) "
175            "[%s]",
176            tr_truncd( 100 * st->percentDone, 1 ),
177            st->peersSendingToUs, st->peersConnected, upStr,
178            st->peersGettingFromUs, dnStr,
179            ratioStr );
180    }
181    else if( st->activity & TR_STATUS_SEED )
182    {
183        char upStr[80];
184        char ratioStr[80];
185
186        tr_formatter_speed_KBps( upStr, st->pieceUploadSpeed_KBps, sizeof( upStr ) );
187        tr_strlratio( ratioStr, st->ratio, sizeof( ratioStr ) );
188
189        tr_snprintf( buf, buflen,
190                     "Seeding, uploading to %d of %d peer(s), %s [%s]",
191                     st->peersGettingFromUs, st->peersConnected, upStr, ratioStr );
192    }
193    else *buf = '\0';
194}
195
196static const char*
197getConfigDir( int argc, const char ** argv )
198{
199    int c;
200    const char * configDir = NULL;
201    const char * optarg;
202    const int ind = tr_optind;
203
204    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ))) {
205        if( c == 'g' ) {
206            configDir = optarg;
207            break;
208        }
209    }
210
211    tr_optind = ind;
212
213    if( configDir == NULL )
214        configDir = tr_getDefaultConfigDir( MY_CONFIG_NAME );
215
216    return configDir;
217}
218
219int
220main( int argc, char ** argv )
221{
222    int           error;
223    tr_session  * h;
224    tr_ctor     * ctor;
225    tr_torrent  * tor = NULL;
226    tr_benc       settings;
227    const char  * configDir;
228    uint8_t     * fileContents;
229    size_t        fileLength;
230
231    tr_formatter_mem_init( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR );
232    tr_formatter_size_init( DISK_K,DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
233    tr_formatter_speed_init( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
234
235    printf( "%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING );
236
237    /* user needs to pass in at least one argument */
238    if( argc < 2 ) {
239        tr_getopt_usage( MY_READABLE_NAME, getUsage( ), options );
240        return EXIT_FAILURE;
241    }
242
243    /* load the defaults from config file + libtransmission defaults */
244    tr_bencInitDict( &settings, 0 );
245    configDir = getConfigDir( argc, (const char**)argv );
246    tr_sessionLoadSettings( &settings, configDir, MY_CONFIG_NAME );
247
248    /* the command line overrides defaults */
249    if( parseCommandLine( &settings, argc, (const char**)argv ) )
250        return EXIT_FAILURE;
251
252    if( showVersion )
253        return 0;
254
255    /* Check the options for validity */
256    if( !torrentPath ) {
257        fprintf( stderr, "No torrent specified!\n" );
258        return EXIT_FAILURE;
259    }
260
261    h = tr_sessionInit( "cli", configDir, FALSE, &settings );
262
263    ctor = tr_ctorNew( h );
264
265    fileContents = tr_loadFile( torrentPath, &fileLength );
266    tr_ctorSetPaused( ctor, TR_FORCE, FALSE );
267    if( fileContents != NULL ) {
268        tr_ctorSetMetainfo( ctor, fileContents, fileLength );
269    } else if( !memcmp( torrentPath, "magnet:?", 8 ) ) {
270        tr_ctorSetMetainfoFromMagnetLink( ctor, torrentPath );
271    } else if( !memcmp( torrentPath, "http", 4 ) ) {
272        tr_webRun( h, torrentPath, NULL, onTorrentFileDownloaded, ctor );
273        waitingOnWeb = TRUE;
274        while( waitingOnWeb ) tr_wait_msec( 1000 );
275    } else {
276        fprintf( stderr, "ERROR: Unrecognized torrent \"%s\".\n", torrentPath );
277        fprintf( stderr, " * If you're trying to create a torrent, use transmission-create.\n" );
278        fprintf( stderr, " * If you're trying to see a torrent's info, use transmission-show.\n" );
279        tr_sessionClose( h );
280        return EXIT_FAILURE;
281    }
282    tr_free( fileContents );
283
284    tor = tr_torrentNew( ctor, &error );
285    tr_ctorFree( ctor );
286    if( !tor )
287    {
288        fprintf( stderr, "Failed opening torrent file `%s'\n", torrentPath );
289        tr_sessionClose( h );
290        return EXIT_FAILURE;
291    }
292
293    signal( SIGINT, sigHandler );
294#ifndef WIN32
295    signal( SIGHUP, sigHandler );
296#endif
297    tr_torrentStart( tor );
298
299    if( verify )
300    {
301        verify = 0;
302        tr_torrentVerify( tor );
303    }
304
305    for( ; ; )
306    {
307        char            line[LINEWIDTH];
308        const tr_stat * st;
309        const char * messageName[] = { NULL, "Tracker gave a warning:",
310                                             "Tracker gave an error:",
311                                             "Error:" };
312
313        tr_wait_msec( 200 );
314
315        if( gotsig )
316        {
317            gotsig = 0;
318            printf( "\nStopping torrent...\n" );
319            tr_torrentStop( tor );
320        }
321
322        if( manualUpdate )
323        {
324            manualUpdate = 0;
325            if( !tr_torrentCanManualUpdate( tor ) )
326                fprintf(
327                    stderr,
328                    "\nReceived SIGHUP, but can't send a manual update now\n" );
329            else
330            {
331                fprintf( stderr,
332                         "\nReceived SIGHUP: manual update scheduled\n" );
333                tr_torrentManualUpdate( tor );
334            }
335        }
336
337        st = tr_torrentStat( tor );
338        if( st->activity & TR_STATUS_STOPPED )
339            break;
340
341        getStatusStr( st, line, sizeof( line ) );
342        printf( "\r%-*s", LINEWIDTH, line );
343
344        if( messageName[st->error] )
345            fprintf( stderr, "\n%s: %s\n", messageName[st->error], st->errorString );
346    }
347
348    tr_sessionSaveSettings( h, configDir, &settings );
349
350    printf( "\n" );
351    tr_bencFree( &settings );
352    tr_sessionClose( h );
353    return EXIT_SUCCESS;
354}
355
356/***
357****
358****
359****
360***/
361
362static int
363parseCommandLine( tr_benc * d, int argc, const char ** argv )
364{
365    int          c;
366    const char * optarg;
367
368    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg )))
369    {
370        switch( c )
371        {
372            case 'b': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, TRUE );
373                      break;
374            case 'B': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
375                      break;
376            case 'd': tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED_KBps, atoi( optarg ) );
377                      tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
378                      break;
379            case 'D': tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
380                      break;
381            case 'f': tr_bencDictAddStr( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, optarg );
382                      tr_bencDictAddBool( d, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, TRUE );
383                      break;
384            case 'g': /* handled above */
385                      break;
386            case 'm': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
387                      break;
388            case 'M': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
389                      break;
390            case 'p': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
391                      break;
392            case 't': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_SOCKET_TOS, atoi( optarg ) );
393                      break;
394            case 'u': tr_bencDictAddInt( d, TR_PREFS_KEY_USPEED_KBps, atoi( optarg ) );
395                      tr_bencDictAddBool( d, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
396                      break;
397            case 'U': tr_bencDictAddBool( d, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
398                      break;
399            case 'v': verify = TRUE;
400                      break;
401            case 'V': showVersion = TRUE;
402                      break;
403            case 'w': tr_bencDictAddStr( d, TR_PREFS_KEY_DOWNLOAD_DIR, optarg );
404                      break;
405            case 910: tr_bencDictAddInt( d, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_REQUIRED );
406                      break;
407            case 911: tr_bencDictAddInt( d, TR_PREFS_KEY_ENCRYPTION, TR_ENCRYPTION_PREFERRED );
408                      break;
409            case 912: tr_bencDictAddInt( d, TR_PREFS_KEY_ENCRYPTION, TR_CLEAR_PREFERRED );
410                      break;
411            case TR_OPT_UNK:
412                      if( torrentPath == NULL )
413                          torrentPath = optarg;
414                      break;
415            default: return 1;
416        }
417    }
418
419    return 0;
420}
421
422static void
423sigHandler( int signal )
424{
425    switch( signal )
426    {
427        case SIGINT:
428            gotsig = 1; break;
429
430#ifndef WIN32
431        case SIGHUP:
432            manualUpdate = 1; break;
433
434#endif
435        default:
436            break;
437    }
438}
439
Note: See TracBrowser for help on using the repository browser.