1
0
Fork 0
mirror of https://github.com/transmission/transmission synced 2024-12-26 17:47:37 +00:00
transmission/utils/show.c
Jordan Lee fbc7f3dc3c (trunk) use base-10 units for network bandwidth (ie, speed) and disk sizes.
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.
2012-02-03 21:21:52 +00:00

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;
}