mirror of
https://github.com/transmission/transmission
synced 2024-12-27 01:57:52 +00:00
87ef45cf3d
I'm less certain that these are unneeded because networking APIs seem to have more variation between platforms, but it's better to remove the cruft and then add back whatever headers $PLATFORM users complain about, than to not remove the cruft at all...
335 lines
9.2 KiB
C
335 lines
9.2 KiB
C
/*
|
|
* This file Copyright (C) Mnemosyne LLC
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2
|
|
* as published by the Free Software Foundation.
|
|
*
|
|
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#include <stdio.h> /* fprintf() */
|
|
#include <string.h> /* strcmp(), strchr(), memcmp() */
|
|
#include <stdlib.h> /* getenv(), qsort() */
|
|
#include <time.h>
|
|
|
|
#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
|
|
#include <curl/curl.h>
|
|
|
|
#include <event2/buffer.h>
|
|
|
|
#include <libtransmission/transmission.h>
|
|
#include <libtransmission/bencode.h>
|
|
#include <libtransmission/tr-getopt.h>
|
|
#include <libtransmission/utils.h>
|
|
#include <libtransmission/web.h> /* tr_webGetResponseStr() */
|
|
#include <libtransmission/version.h>
|
|
|
|
#define MY_NAME "transmission-show"
|
|
#define TIMEOUT_SECS 30
|
|
|
|
#define MEM_K 1024
|
|
#define MEM_K_STR "KiB"
|
|
#define MEM_M_STR "MiB"
|
|
#define MEM_G_STR "GiB"
|
|
#define MEM_T_STR "TiB"
|
|
|
|
#define DISK_K 1024
|
|
#define DISK_B_STR "B"
|
|
#define DISK_K_STR "KiB"
|
|
#define DISK_M_STR "MiB"
|
|
#define DISK_G_STR "GiB"
|
|
#define DISK_T_STR "TiB"
|
|
|
|
#define SPEED_K 1024
|
|
#define SPEED_B_STR "B/s"
|
|
#define SPEED_K_STR "KiB/s"
|
|
#define SPEED_M_STR "MiB/s"
|
|
#define SPEED_G_STR "GiB/s"
|
|
#define SPEED_T_STR "TiB/s"
|
|
|
|
static tr_option options[] =
|
|
{
|
|
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", 0, NULL },
|
|
{ 'V', "version", "Show version number and exit", "V", 0, NULL },
|
|
{ 0, NULL, NULL, NULL, 0, NULL }
|
|
};
|
|
|
|
static const char *
|
|
getUsage( void )
|
|
{
|
|
return "Usage: " MY_NAME " [options] <.torrent file>";
|
|
}
|
|
|
|
static bool scrapeFlag = false;
|
|
static bool showVersion = false;
|
|
const char * filename = NULL;
|
|
|
|
static int
|
|
parseCommandLine( int argc, const char ** argv )
|
|
{
|
|
int c;
|
|
const char * optarg;
|
|
|
|
while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg )))
|
|
{
|
|
switch( c )
|
|
{
|
|
case 's': scrapeFlag = true; break;
|
|
case 'V': showVersion = true; break;
|
|
case TR_OPT_UNK: filename = optarg; break;
|
|
default: return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
compare_files_by_name( const void * va, const void * vb )
|
|
{
|
|
const tr_file * a = *(const tr_file**)va;
|
|
const tr_file * b = *(const tr_file**)vb;
|
|
return strcmp( a->name, b->name );
|
|
}
|
|
|
|
static void
|
|
showInfo( const tr_info * inf )
|
|
{
|
|
int i;
|
|
char buf[128];
|
|
tr_file ** files;
|
|
int prevTier = -1;
|
|
|
|
/**
|
|
*** General Info
|
|
**/
|
|
|
|
printf( "GENERAL\n\n" );
|
|
printf( " Name: %s\n", inf->name );
|
|
printf( " Hash: %s\n", inf->hashString );
|
|
printf( " Created by: %s\n", inf->creator ? inf->creator : "Unknown" );
|
|
if( !inf->dateCreated )
|
|
printf( " Created on: Unknown\n" );
|
|
else {
|
|
struct tm tm = *localtime( &inf->dateCreated );
|
|
printf( " Created on: %s", asctime( &tm ) );
|
|
}
|
|
if( inf->comment && *inf->comment )
|
|
printf( " Comment: %s\n", inf->comment );
|
|
printf( " Piece Count: %d\n", inf->pieceCount );
|
|
printf( " Piece Size: %s\n", tr_formatter_mem_B( buf, inf->pieceSize, sizeof( buf ) ) );
|
|
printf( " Total Size: %s\n", tr_formatter_size_B( buf, inf->totalSize, sizeof( buf ) ) );
|
|
printf( " Privacy: %s\n", inf->isPrivate ? "Private torrent" : "Public torrent" );
|
|
|
|
/**
|
|
*** Trackers
|
|
**/
|
|
|
|
printf( "\nTRACKERS\n" );
|
|
for( i=0; i<inf->trackerCount; ++i )
|
|
{
|
|
if( prevTier != inf->trackers[i].tier )
|
|
{
|
|
prevTier = inf->trackers[i].tier;
|
|
printf( "\n Tier #%d\n", prevTier + 1 );
|
|
}
|
|
|
|
printf( " %s\n", inf->trackers[i].announce );
|
|
}
|
|
|
|
/**
|
|
***
|
|
**/
|
|
|
|
if( inf->webseedCount > 0 )
|
|
{
|
|
printf( "\nWEBSEEDS\n\n" );
|
|
|
|
for( i=0; i<inf->webseedCount; ++i )
|
|
printf( " %s\n", inf->webseeds[i] );
|
|
}
|
|
|
|
/**
|
|
*** Files
|
|
**/
|
|
|
|
printf( "\nFILES\n\n" );
|
|
files = tr_new( tr_file*, inf->fileCount );
|
|
for( i=0; i<(int)inf->fileCount; ++i )
|
|
files[i] = &inf->files[i];
|
|
qsort( files, inf->fileCount, sizeof(tr_file*), compare_files_by_name );
|
|
for( i=0; i<(int)inf->fileCount; ++i )
|
|
printf( " %s (%s)\n", files[i]->name, tr_formatter_size_B( buf, files[i]->length, sizeof( buf ) ) );
|
|
tr_free( files );
|
|
}
|
|
|
|
static size_t
|
|
writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
|
|
{
|
|
const size_t byteCount = size * nmemb;
|
|
evbuffer_add( buf, ptr, byteCount );
|
|
return byteCount;
|
|
}
|
|
|
|
static CURL*
|
|
tr_curl_easy_init( struct evbuffer * writebuf )
|
|
{
|
|
CURL * curl = curl_easy_init( );
|
|
curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
|
|
curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
|
|
curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
|
|
curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
|
|
curl_easy_setopt( curl, CURLOPT_VERBOSE, getenv( "TR_CURL_VERBOSE" ) != NULL );
|
|
curl_easy_setopt( curl, CURLOPT_ENCODING, "" );
|
|
return curl;
|
|
}
|
|
|
|
static void
|
|
doScrape( const tr_info * inf )
|
|
{
|
|
int i;
|
|
|
|
for( i=0; i<inf->trackerCount; ++i )
|
|
{
|
|
CURL * curl;
|
|
CURLcode res;
|
|
struct evbuffer * buf;
|
|
const char * scrape = inf->trackers[i].scrape;
|
|
char * url;
|
|
char escaped[SHA_DIGEST_LENGTH*3 + 1];
|
|
|
|
if( scrape == NULL )
|
|
continue;
|
|
|
|
tr_http_escape_sha1( escaped, inf->hash );
|
|
|
|
url = tr_strdup_printf( "%s%cinfo_hash=%s",
|
|
scrape,
|
|
strchr( scrape, '?' ) ? '&' : '?',
|
|
escaped );
|
|
|
|
printf( "%s ... ", url );
|
|
fflush( stdout );
|
|
|
|
buf = evbuffer_new( );
|
|
curl = tr_curl_easy_init( buf );
|
|
curl_easy_setopt( curl, CURLOPT_URL, url );
|
|
curl_easy_setopt( curl, CURLOPT_TIMEOUT, TIMEOUT_SECS );
|
|
|
|
if(( res = curl_easy_perform( curl )))
|
|
{
|
|
printf( "error: %s\n", curl_easy_strerror( res ) );
|
|
}
|
|
else
|
|
{
|
|
long response;
|
|
curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
|
|
if( response != 200 )
|
|
{
|
|
printf( "error: unexpected response %ld \"%s\"\n",
|
|
response,
|
|
tr_webGetResponseStr( response ) );
|
|
}
|
|
else /* HTTP OK */
|
|
{
|
|
tr_benc top;
|
|
tr_benc * files;
|
|
bool matched = false;
|
|
const char * begin = (const char*) evbuffer_pullup( buf, -1 );
|
|
const char * end = begin + evbuffer_get_length( buf );
|
|
|
|
if( !tr_bencParse( begin, end, &top, NULL ) )
|
|
{
|
|
if( tr_bencDictFindDict( &top, "files", &files ) )
|
|
{
|
|
int i = 0;
|
|
tr_benc * val;
|
|
const char * key;
|
|
|
|
while( tr_bencDictChild( files, i++, &key, &val ))
|
|
{
|
|
if( !memcmp( inf->hash, key, SHA_DIGEST_LENGTH ) )
|
|
{
|
|
int64_t seeders = -1;
|
|
int64_t leechers = -1;
|
|
tr_bencDictFindInt( val, "complete", &seeders );
|
|
tr_bencDictFindInt( val, "incomplete", &leechers );
|
|
printf( "%d seeders, %d leechers\n", (int)seeders, (int)leechers );
|
|
matched = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
tr_bencFree( &top );
|
|
}
|
|
|
|
if( !matched )
|
|
printf( "no match\n" );
|
|
}
|
|
}
|
|
|
|
curl_easy_cleanup( curl );
|
|
evbuffer_free( buf );
|
|
tr_free( url );
|
|
}
|
|
}
|
|
|
|
int
|
|
main( int argc, char * argv[] )
|
|
{
|
|
int err;
|
|
tr_info inf;
|
|
tr_ctor * ctor;
|
|
|
|
tr_setMessageLevel( TR_MSG_ERR );
|
|
tr_formatter_mem_init ( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR );
|
|
tr_formatter_size_init ( DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
|
|
tr_formatter_speed_init ( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
|
|
|
|
if( parseCommandLine( argc, (const char**)argv ) )
|
|
return EXIT_FAILURE;
|
|
|
|
if( showVersion )
|
|
{
|
|
fprintf( stderr, MY_NAME" "LONG_VERSION_STRING"\n" );
|
|
return 0;
|
|
}
|
|
|
|
/* make sure the user specified a filename */
|
|
if( !filename )
|
|
{
|
|
fprintf( stderr, "ERROR: No .torrent file specified.\n" );
|
|
tr_getopt_usage( MY_NAME, getUsage( ), options );
|
|
fprintf( stderr, "\n" );
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
/* try to parse the .torrent file */
|
|
ctor = tr_ctorNew( NULL );
|
|
tr_ctorSetMetainfoFromFile( ctor, filename );
|
|
err = tr_torrentParse( ctor, &inf );
|
|
tr_ctorFree( ctor );
|
|
if( err )
|
|
{
|
|
fprintf( stderr, "Error parsing .torrent file \"%s\"\n", filename );
|
|
return 1;
|
|
}
|
|
|
|
printf( "Name: %s\n", inf.name );
|
|
printf( "File: %s\n", filename );
|
|
printf( "\n" );
|
|
fflush( stdout );
|
|
|
|
if( scrapeFlag )
|
|
doScrape( &inf );
|
|
else
|
|
showInfo( &inf );
|
|
|
|
/* cleanup */
|
|
putc( '\n', stdout );
|
|
tr_metainfoFree( &inf );
|
|
return 0;
|
|
}
|