mirror of
https://github.com/transmission/transmission
synced 2024-12-26 17:47:37 +00:00
fbc7f3dc3c
It looks like the Mac client is already doing this and it's clearly the trend in other apps as well. Even apt-get is using kB/s, ferchrissake... :) Flame away.
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 1000
|
|
#define DISK_B_STR "B"
|
|
#define DISK_K_STR "kB"
|
|
#define DISK_M_STR "MB"
|
|
#define DISK_G_STR "GB"
|
|
#define DISK_T_STR "TB"
|
|
|
|
#define SPEED_K 1000
|
|
#define SPEED_B_STR "B/s"
|
|
#define SPEED_K_STR "kB/s"
|
|
#define SPEED_M_STR "MB/s"
|
|
#define SPEED_G_STR "GB/s"
|
|
#define SPEED_T_STR "TB/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;
|
|
}
|