1/*
2 * This file Copyright (C) Mnemosyne LLC
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2
6 * as published by the Free Software Foundation.
7 *
8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
9 *
10 * $Id: show.c 13475 2012-09-07 04:25:04Z jordan $
11 */
12
13#include <stdio.h> /* fprintf() */
14#include <string.h> /* strcmp(), strchr(), memcmp() */
15#include <stdlib.h> /* getenv(), qsort() */
16#include <time.h>
17
18#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
19#include <curl/curl.h>
20
21#include <event2/buffer.h>
22
23#include <libtransmission/transmission.h>
24#include <libtransmission/bencode.h>
25#include <libtransmission/tr-getopt.h>
26#include <libtransmission/utils.h>
27#include <libtransmission/web.h> /* tr_webGetResponseStr() */
28#include <libtransmission/version.h>
29
30#define MY_NAME "transmission-show"
31#define TIMEOUT_SECS 30
32
33#define MEM_K 1024
34#define MEM_K_STR "KiB"
35#define MEM_M_STR "MiB"
36#define MEM_G_STR "GiB"
37#define MEM_T_STR "TiB"
38
39#define DISK_K 1000
40#define DISK_B_STR  "B"
41#define DISK_K_STR "kB"
42#define DISK_M_STR "MB"
43#define DISK_G_STR "GB"
44#define DISK_T_STR "TB"
45
46#define SPEED_K 1000
47#define SPEED_B_STR  "B/s"
48#define SPEED_K_STR "kB/s"
49#define SPEED_M_STR "MB/s"
50#define SPEED_G_STR "GB/s"
51#define SPEED_T_STR "TB/s"
52
53static tr_option options[] =
54{
55  { 'm', "magnet", "Give a magnet link for the specified torrent", "m", 0, NULL },
56  { 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", 0, NULL },
57  { 'V', "version", "Show version number and exit", "V", 0, NULL },
58  { 0, NULL, NULL, NULL, 0, NULL }
59};
60
61static const char *
62getUsage( void )
63{
64    return "Usage: " MY_NAME " [options] <.torrent file>";
65}
66
67static bool magnetFlag = false;
68static bool scrapeFlag = false;
69static bool showVersion = false;
70const char * filename = NULL;
71
72static int
73parseCommandLine( int argc, const char ** argv )
74{
75    int c;
76    const char * optarg;
77
78    while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg )))
79    {
80        switch( c )
81        {
82            case 'm': magnetFlag = true; break;
83            case 's': scrapeFlag = true; break;
84            case 'V': showVersion = true; break;
85            case TR_OPT_UNK: filename = optarg; break;
86            default: return 1;
87        }
88    }
89
90    return 0;
91}
92
93static void
94doShowMagnet( const tr_info * inf )
95{
96    char * str = tr_torrentInfoGetMagnetLink( inf );
97    printf( "%s", str );
98    tr_free( str );
99}
100
101static int
102compare_files_by_name( const void * va, const void * vb )
103{
104    const tr_file * a = *(const tr_file**)va;
105    const tr_file * b = *(const tr_file**)vb;
106    return strcmp( a->name, b->name );
107}
108
109static void
110showInfo( const tr_info * inf )
111{
112    int i;
113    char buf[128];
114    tr_file ** files;
115    int prevTier = -1;
116
117    /**
118    ***  General Info
119    **/
120
121    printf( "GENERAL\n\n" );
122    printf( "  Name: %s\n", inf->name );
123    printf( "  Hash: %s\n", inf->hashString );
124    printf( "  Created by: %s\n", inf->creator ? inf->creator : "Unknown" );
125    if( !inf->dateCreated )
126        printf( "  Created on: Unknown\n" );
127    else {
128        struct tm tm = *localtime( &inf->dateCreated );
129        printf( "  Created on: %s", asctime( &tm ) );
130    }
131    if( inf->comment && *inf->comment )
132        printf( "  Comment: %s\n", inf->comment );
133    printf( "  Piece Count: %d\n", inf->pieceCount );
134    printf( "  Piece Size: %s\n", tr_formatter_mem_B( buf, inf->pieceSize, sizeof( buf ) ) );
135    printf( "  Total Size: %s\n", tr_formatter_size_B( buf, inf->totalSize, sizeof( buf ) ) );
136    printf( "  Privacy: %s\n", inf->isPrivate ? "Private torrent" : "Public torrent" );
137
138    /**
139    ***  Trackers
140    **/
141
142    printf( "\nTRACKERS\n" );
143    for( i=0; i<inf->trackerCount; ++i )
144    {
145        if( prevTier != inf->trackers[i].tier )
146        {
147            prevTier = inf->trackers[i].tier;
148            printf( "\n  Tier #%d\n", prevTier + 1 );
149        }
150
151        printf( "  %s\n", inf->trackers[i].announce );
152    }
153
154    /**
155    ***
156    **/
157
158    if( inf->webseedCount > 0 )
159    {
160        printf( "\nWEBSEEDS\n\n" );
161
162        for( i=0; i<inf->webseedCount; ++i )
163            printf( "  %s\n", inf->webseeds[i] );
164    }
165
166    /**
167    ***  Files
168    **/
169
170    printf( "\nFILES\n\n" );
171    files = tr_new( tr_file*, inf->fileCount );
172    for( i=0; i<(int)inf->fileCount; ++i )
173        files[i] = &inf->files[i];
174    qsort( files, inf->fileCount, sizeof(tr_file*), compare_files_by_name );
175    for( i=0; i<(int)inf->fileCount; ++i )
176        printf( "  %s (%s)\n", files[i]->name, tr_formatter_size_B( buf, files[i]->length, sizeof( buf ) ) );
177    tr_free( files );
178}
179
180static size_t
181writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
182{
183    const size_t byteCount = size * nmemb;
184    evbuffer_add( buf, ptr, byteCount );
185    return byteCount;
186}
187
188static CURL*
189tr_curl_easy_init( struct evbuffer * writebuf )
190{
191    CURL * curl = curl_easy_init( );
192    curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
193    curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
194    curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
195    curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
196    curl_easy_setopt( curl, CURLOPT_VERBOSE, getenv( "TR_CURL_VERBOSE" ) != NULL );
197    curl_easy_setopt( curl, CURLOPT_ENCODING, "" );
198    return curl;
199}
200
201static void
202doScrape( const tr_info * inf )
203{
204    int i;
205
206    for( i=0; i<inf->trackerCount; ++i )
207    {
208        CURL * curl;
209        CURLcode res;
210        struct evbuffer * buf;
211        const char * scrape = inf->trackers[i].scrape;
212        char * url;
213        char escaped[SHA_DIGEST_LENGTH*3 + 1];
214
215        if( scrape == NULL )
216            continue;
217
218        tr_http_escape_sha1( escaped, inf->hash );
219
220        url = tr_strdup_printf( "%s%cinfo_hash=%s",
221                                scrape,
222                                strchr( scrape, '?' ) ? '&' : '?',
223                                escaped );
224
225        printf( "%s ... ", url );
226        fflush( stdout );
227
228        buf = evbuffer_new( );
229        curl = tr_curl_easy_init( buf );
230        curl_easy_setopt( curl, CURLOPT_URL, url );
231        curl_easy_setopt( curl, CURLOPT_TIMEOUT, TIMEOUT_SECS );
232
233        if(( res = curl_easy_perform( curl )))
234        {
235            printf( "error: %s\n", curl_easy_strerror( res ) );
236        }
237        else
238        {
239            long response;
240            curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
241            if( response != 200 )
242            {
243                printf( "error: unexpected response %ld \"%s\"\n",
244                        response,
245                        tr_webGetResponseStr( response ) );
246            }
247            else /* HTTP OK */
248            {
249                tr_benc top;
250                tr_benc * files;
251                bool matched = false;
252                const char * begin = (const char*) evbuffer_pullup( buf, -1 );
253                const char * end = begin + evbuffer_get_length( buf );
254
255                if( !tr_bencParse( begin, end, &top, NULL ) )
256                {
257                    if( tr_bencDictFindDict( &top, "files", &files ) )
258                    {
259                        int i = 0;
260                        tr_benc * val;
261                        const char * key;
262
263                        while( tr_bencDictChild( files, i++, &key, &val ))
264                        {
265                            if( !memcmp( inf->hash, key, SHA_DIGEST_LENGTH ) )
266                            {
267                                int64_t seeders = -1;
268                                int64_t leechers = -1;
269                                tr_bencDictFindInt( val, "complete", &seeders );
270                                tr_bencDictFindInt( val, "incomplete", &leechers );
271                                printf( "%d seeders, %d leechers\n", (int)seeders, (int)leechers );
272                                matched = true;
273                            }
274                        }
275                    }
276
277                    tr_bencFree( &top );
278                }
279
280                if( !matched )
281                    printf( "no match\n" );
282            }
283        }
284
285        curl_easy_cleanup( curl );
286        evbuffer_free( buf );
287        tr_free( url );
288    }
289}
290
291int
292main( int argc, char * argv[] )
293{
294    int err;
295    tr_info inf;
296    tr_ctor * ctor;
297
298    tr_setMessageLevel( TR_MSG_ERR );
299    tr_formatter_mem_init  ( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR );
300    tr_formatter_size_init ( DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
301    tr_formatter_speed_init ( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
302
303    if( parseCommandLine( argc, (const char**)argv ) )
304        return EXIT_FAILURE;
305
306    if( showVersion )
307    {
308        fprintf( stderr, MY_NAME" "LONG_VERSION_STRING"\n" );
309        return 0;
310    }
311
312    /* make sure the user specified a filename */
313    if( !filename )
314    {
315        fprintf( stderr, "ERROR: No .torrent file specified.\n" );
316        tr_getopt_usage( MY_NAME, getUsage( ), options );
317        fprintf( stderr, "\n" );
318        return EXIT_FAILURE;
319    }
320
321    /* try to parse the .torrent file */
322    ctor = tr_ctorNew( NULL );
323    tr_ctorSetMetainfoFromFile( ctor, filename );
324    err = tr_torrentParse( ctor, &inf );
325    tr_ctorFree( ctor );
326    if( err )
327    {
328        fprintf( stderr, "Error parsing .torrent file \"%s\"\n", filename );
329        return 1;
330    }
331
332    if( magnetFlag )
333    {
334        doShowMagnet( &inf );
335    }
336    else
337    {
338        printf( "Name: %s\n", inf.name );
339        printf( "File: %s\n", filename );
340        printf( "\n" );
341        fflush( stdout );
342
343        if( scrapeFlag )
344            doScrape( &inf );
345        else
346            showInfo( &inf );
347    }
348
349    /* cleanup */
350    putc( '\n', stdout );
351    tr_metainfoFree( &inf );
352    return 0;
353}
354