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