(trunk) #2983: add command-line utilities for creating .torrent files, for editing passkeys, for adding/removing trackers, etc.
This commit is contained in:
parent
71c1919cfc
commit
eda211e5ce
|
@ -19,6 +19,7 @@ SUBDIRS = \
|
|||
doc \
|
||||
third-party \
|
||||
libtransmission \
|
||||
utils \
|
||||
$(DAEMON_DIR) \
|
||||
$(CLI_DIR) \
|
||||
$(GTK_DIR) \
|
||||
|
|
219
cli/cli.c
219
cli/cli.c
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/bencode.h>
|
||||
#include <libtransmission/makemeta.h>
|
||||
#include <libtransmission/tr-getopt.h>
|
||||
#include <libtransmission/utils.h> /* tr_wait_msec */
|
||||
#include <libtransmission/version.h>
|
||||
|
@ -39,27 +38,16 @@
|
|||
#define LINEWIDTH 80
|
||||
#define MY_NAME "transmissioncli"
|
||||
|
||||
static tr_bool showInfo = 0;
|
||||
static tr_bool showScrape = 0;
|
||||
static tr_bool isPrivate = 0;
|
||||
static tr_bool verify = 0;
|
||||
static sig_atomic_t gotsig = 0;
|
||||
static sig_atomic_t manualUpdate = 0;
|
||||
|
||||
static const char * torrentPath = NULL;
|
||||
static const char * sourceFile = NULL;
|
||||
static const char * comment = NULL;
|
||||
|
||||
#define MAX_ANNOUNCE 128
|
||||
static tr_tracker_info announce[MAX_ANNOUNCE];
|
||||
static int announceCount = 0;
|
||||
|
||||
static const struct tr_option options[] =
|
||||
{
|
||||
{ 'a', "announce", "Set the new torrent's announce URL", "a", 1, "<url>" },
|
||||
{ 'b', "blocklist", "Enable peer blocklists", "b", 0, NULL },
|
||||
{ 'B', "no-blocklist", "Disable peer blocklists", "B", 0, NULL },
|
||||
{ 'c', "comment", "Set the new torrent's comment", "c", 1, "<comment>" },
|
||||
{ 'd', "downlimit", "Set max download speed in KiB/s", "d", 1, "<speed>" },
|
||||
{ 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL },
|
||||
{ 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL },
|
||||
|
@ -67,13 +55,9 @@ static const struct tr_option options[] =
|
|||
{ 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
|
||||
{ 'f', "finish", "Run a script when the torrent finishes", "f", 1, "<script>" },
|
||||
{ 'g', "config-dir", "Where to find configuration files", "g", 1, "<path>" },
|
||||
{ 'i', "info", "Show torrent details and exit", "i", 0, NULL },
|
||||
{ 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
|
||||
{ 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
|
||||
{ 'n', "new", "Create a new torrent", "n", 1, "<source>" },
|
||||
{ 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
|
||||
{ 'r', "private", "Set the new torrent's 'private' flag", "r", 0, NULL },
|
||||
{ 's', "scrape", "Scrape the torrent and exit", "s", 0, NULL },
|
||||
{ 't', "tos", "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR ")", "t", 1, "<tos>" },
|
||||
{ 'u', "uplimit", "Set max upload speed in KiB/s", "u", 1, "<speed>" },
|
||||
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", 0, NULL },
|
||||
|
@ -112,30 +96,6 @@ tr_strlratio( char * buf,
|
|||
return buf;
|
||||
}
|
||||
|
||||
static int
|
||||
is_rfc2396_alnum( char ch )
|
||||
{
|
||||
return ( '0' <= ch && ch <= '9' )
|
||||
|| ( 'A' <= ch && ch <= 'Z' )
|
||||
|| ( 'a' <= ch && ch <= 'z' );
|
||||
}
|
||||
|
||||
static void
|
||||
escape( char * out,
|
||||
const uint8_t * in,
|
||||
int in_len ) /* rfc2396 */
|
||||
{
|
||||
const uint8_t *end = in + in_len;
|
||||
|
||||
while( in != end )
|
||||
if( is_rfc2396_alnum( *in ) )
|
||||
*out++ = (char) *in++;
|
||||
else
|
||||
out += tr_snprintf( out, 4, "%%%02X", (unsigned int)*in++ );
|
||||
|
||||
*out = '\0';
|
||||
}
|
||||
|
||||
static tr_bool waitingOnWeb;
|
||||
|
||||
static void
|
||||
|
@ -149,83 +109,6 @@ onTorrentFileDownloaded( tr_session * session UNUSED,
|
|||
waitingOnWeb = FALSE;
|
||||
}
|
||||
|
||||
static int leftToScrape = 0;
|
||||
|
||||
static void
|
||||
scrapeDoneFunc( tr_session * session UNUSED,
|
||||
long response_code,
|
||||
const void * response,
|
||||
size_t response_byte_count,
|
||||
void * host )
|
||||
{
|
||||
tr_benc top, *files;
|
||||
|
||||
if( !tr_bencLoad( response, response_byte_count, &top, NULL )
|
||||
&& tr_bencDictFindDict( &top, "files", &files )
|
||||
&& files->val.l.count >= 2 )
|
||||
{
|
||||
int64_t complete = -1, incomplete = -1, downloaded = -1;
|
||||
tr_benc * hash = &files->val.l.vals[1];
|
||||
tr_bencDictFindInt( hash, "complete", &complete );
|
||||
tr_bencDictFindInt( hash, "incomplete", &incomplete );
|
||||
tr_bencDictFindInt( hash, "downloaded", &downloaded );
|
||||
printf( "%4d seeders, %4d leechers, %5d downloads at %s\n",
|
||||
(int)complete, (int)incomplete, (int)downloaded,
|
||||
(char*)host );
|
||||
tr_bencFree( &top );
|
||||
}
|
||||
else
|
||||
fprintf( stderr, "Unable to parse response (http code %lu) at %s",
|
||||
response_code,
|
||||
(char*)host );
|
||||
|
||||
--leftToScrape;
|
||||
|
||||
tr_free( host );
|
||||
}
|
||||
|
||||
static void
|
||||
dumpInfo( FILE * out,
|
||||
const tr_info * inf )
|
||||
{
|
||||
int i;
|
||||
int prevTier = -1;
|
||||
tr_file_index_t ff;
|
||||
|
||||
fprintf( out, "hash:\t" );
|
||||
for( i = 0; i < SHA_DIGEST_LENGTH; ++i )
|
||||
fprintf( out, "%02x", inf->hash[i] );
|
||||
fprintf( out, "\n" );
|
||||
|
||||
fprintf( out, "name:\t%s\n", inf->name );
|
||||
|
||||
for( i = 0; i < inf->trackerCount; ++i )
|
||||
{
|
||||
if( prevTier != inf->trackers[i].tier )
|
||||
{
|
||||
prevTier = inf->trackers[i].tier;
|
||||
fprintf( out, "\ntracker tier #%d:\n", ( prevTier + 1 ) );
|
||||
}
|
||||
fprintf( out, "\tannounce:\t%s\n", inf->trackers[i].announce );
|
||||
}
|
||||
|
||||
fprintf( out, "size:\t%" PRIu64 " (%" PRIu64 " * %d + %" PRIu64 ")\n",
|
||||
inf->totalSize, inf->totalSize / inf->pieceSize,
|
||||
inf->pieceSize, inf->totalSize % inf->pieceSize );
|
||||
|
||||
if( inf->comment && *inf->comment )
|
||||
fprintf( out, "comment:\t%s\n", inf->comment );
|
||||
if( inf->creator && *inf->creator )
|
||||
fprintf( out, "creator:\t%s\n", inf->creator );
|
||||
if( inf->isPrivate )
|
||||
fprintf( out, "private flag set\n" );
|
||||
|
||||
fprintf( out, "file(s):\n" );
|
||||
for( ff = 0; ff < inf->fileCount; ++ff )
|
||||
fprintf( out, "\t%s (%" PRIu64 ")\n", inf->files[ff].name,
|
||||
inf->files[ff].length );
|
||||
}
|
||||
|
||||
static void
|
||||
getStatusStr( const tr_stat * st,
|
||||
char * buf,
|
||||
|
@ -304,8 +187,6 @@ main( int argc,
|
|||
tr_torrent * tor = NULL;
|
||||
tr_benc settings;
|
||||
const char * configDir;
|
||||
tr_bool haveSource;
|
||||
tr_bool haveAnnounce;
|
||||
uint8_t * fileContents;
|
||||
size_t fileLength;
|
||||
|
||||
|
@ -333,42 +214,12 @@ main( int argc,
|
|||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
/* don't bind the port if we're just running the CLI
|
||||
to get metainfo or to create a torrent */
|
||||
if( showInfo || showScrape || ( sourceFile != NULL ) )
|
||||
tr_bencDictAddInt( &settings, TR_PREFS_KEY_PEER_PORT, -1 );
|
||||
|
||||
h = tr_sessionInit( "cli", configDir, FALSE, &settings );
|
||||
|
||||
haveSource = sourceFile && *sourceFile;
|
||||
haveAnnounce = announceCount > 0;
|
||||
|
||||
if( haveSource && !haveAnnounce )
|
||||
fprintf( stderr, "Did you mean to create a torrent without a tracker's announce URL?\n" );
|
||||
|
||||
if( haveSource ) /* creating a torrent */
|
||||
{
|
||||
int err;
|
||||
tr_metainfo_builder * b;
|
||||
fprintf( stderr, "creating torrent \"%s\"\n", torrentPath );
|
||||
|
||||
b = tr_metaInfoBuilderCreate( sourceFile );
|
||||
tr_makeMetaInfo( b, torrentPath, announce, announceCount, comment, isPrivate );
|
||||
while( !b->isDone )
|
||||
{
|
||||
tr_wait_msec( 1000 );
|
||||
printf( "." );
|
||||
}
|
||||
|
||||
err = b->result;
|
||||
tr_metaInfoBuilderFree( b );
|
||||
return err;
|
||||
}
|
||||
|
||||
ctor = tr_ctorNew( h );
|
||||
|
||||
fileContents = tr_loadFile( torrentPath, &fileLength );
|
||||
tr_ctorSetPaused( ctor, TR_FORCE, showScrape );
|
||||
tr_ctorSetPaused( ctor, TR_FORCE, FALSE );
|
||||
if( fileContents != NULL ) {
|
||||
tr_ctorSetMetainfo( ctor, fileContents, fileLength );
|
||||
} else if( !memcmp( torrentPath, "magnet:?", 8 ) ) {
|
||||
|
@ -380,57 +231,6 @@ main( int argc,
|
|||
}
|
||||
tr_free( fileContents );
|
||||
|
||||
if( showScrape )
|
||||
{
|
||||
tr_info info;
|
||||
|
||||
if( !tr_torrentParse( ctor, &info ) )
|
||||
{
|
||||
int i;
|
||||
const time_t start = time( NULL );
|
||||
for( i = 0; i < info.trackerCount; ++i )
|
||||
{
|
||||
if( info.trackers[i].scrape )
|
||||
{
|
||||
const char * scrape = info.trackers[i].scrape;
|
||||
char escaped[SHA_DIGEST_LENGTH * 3 + 1];
|
||||
char * url, *host;
|
||||
escape( escaped, info.hash, SHA_DIGEST_LENGTH );
|
||||
url = tr_strdup_printf( "%s%cinfo_hash=%s",
|
||||
scrape,
|
||||
strchr( scrape,
|
||||
'?' ) ? '&' : '?',
|
||||
escaped );
|
||||
tr_urlParse( scrape, -1, NULL, &host, NULL, NULL );
|
||||
++leftToScrape;
|
||||
|
||||
tr_webRun( h, url, NULL, scrapeDoneFunc, host );
|
||||
tr_free( url );
|
||||
}
|
||||
}
|
||||
|
||||
fprintf( stderr, "scraping %d trackers:\n", leftToScrape );
|
||||
|
||||
while( leftToScrape > 0 && ( ( time( NULL ) - start ) < 20 ) )
|
||||
tr_wait_msec( 250 );
|
||||
}
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if( showInfo )
|
||||
{
|
||||
tr_info info;
|
||||
|
||||
if( !tr_torrentParse( ctor, &info ) )
|
||||
{
|
||||
dumpInfo( stdout, &info );
|
||||
tr_metainfoFree( &info );
|
||||
}
|
||||
|
||||
tr_ctorFree( ctor );
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
tor = tr_torrentNew( ctor, &error );
|
||||
tr_ctorFree( ctor );
|
||||
if( !tor )
|
||||
|
@ -495,8 +295,6 @@ main( int argc,
|
|||
fprintf( stderr, "\n%s: %s\n", messageName[st->error], st->errorString );
|
||||
}
|
||||
|
||||
cleanup:
|
||||
|
||||
tr_sessionSaveSettings( h, configDir, &settings );
|
||||
|
||||
printf( "\n" );
|
||||
|
@ -521,18 +319,10 @@ parseCommandLine( tr_benc * d, int argc, const char ** argv )
|
|||
{
|
||||
switch( c )
|
||||
{
|
||||
case 'a': if( announceCount + 1 < MAX_ANNOUNCE ) {
|
||||
announce[announceCount].tier = announceCount;
|
||||
announce[announceCount].announce = (char*) optarg;
|
||||
++announceCount;
|
||||
}
|
||||
break;
|
||||
case 'b': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, TRUE );
|
||||
break;
|
||||
case 'B': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
|
||||
break;
|
||||
case 'c': comment = optarg;
|
||||
break;
|
||||
case 'd': tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED, atoi( optarg ) );
|
||||
tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
|
||||
break;
|
||||
|
@ -543,19 +333,12 @@ parseCommandLine( tr_benc * d, int argc, const char ** argv )
|
|||
break;
|
||||
case 'g': /* handled above */
|
||||
break;
|
||||
case 'i': showInfo = 1;
|
||||
break;
|
||||
case 'm': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
|
||||
break;
|
||||
case 'M': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
|
||||
break;
|
||||
case 'n': sourceFile = optarg; break;
|
||||
case 'p': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
|
||||
break;
|
||||
case 'r': isPrivate = 1;
|
||||
break;
|
||||
case 's': showScrape = 1;
|
||||
break;
|
||||
case 't': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_SOCKET_TOS, atoi( optarg ) );
|
||||
break;
|
||||
case 'u': tr_bencDictAddInt( d, TR_PREFS_KEY_USPEED, atoi( optarg ) );
|
||||
|
|
|
@ -24,18 +24,6 @@
|
|||
.Bk -words
|
||||
.Fl h
|
||||
.Nm
|
||||
.Fl i
|
||||
.Ar torrent-file
|
||||
.Nm
|
||||
.Fl s
|
||||
.Ar torrent-file
|
||||
.Nm
|
||||
.Fl n Ar sourcefile
|
||||
.Op Fl a Ar url
|
||||
.Op Fl c Ar comment
|
||||
.Op Fl r
|
||||
.Ar new-torrent-file
|
||||
.Nm
|
||||
.Op Fl b | B
|
||||
.Op Fl d Ar number | Fl D
|
||||
.Op Fl er | ep | et
|
||||
|
@ -58,15 +46,11 @@ scripting capabilities.
|
|||
.Pp
|
||||
The options are as follows:
|
||||
.Bl -tag -width Ds
|
||||
.It Fl a, Fl -announce Ar announce-url
|
||||
When creating a new torrent, set its announce URL
|
||||
.It Fl b Fl -blocklist
|
||||
Enable peer blocklists. Transmission understands the bluetack blocklist file format.
|
||||
New blocklists can be added by copying them into the config-dir's "blocklists" subdirectory.
|
||||
.It Fl B Fl -no-blocklist
|
||||
Disble blocklists.
|
||||
.It Fl c, Fl -comment Ar comment-text
|
||||
When creating a new torrent, set its comment field
|
||||
.It Fl d, -downlimit Ar number
|
||||
Set the maximum download speed in KB/s
|
||||
.It Fl D, -no-downlimit
|
||||
|
@ -84,8 +68,6 @@ Where to look for configuration files. This can be used to swap between using t
|
|||
See http://trac.transmissionbt.com/wiki/ConfigFiles for more information.
|
||||
.It Fl h, Fl -help
|
||||
Prints a short usage summary.
|
||||
.It Fl i, Fl -info
|
||||
Shows torrent details and exit
|
||||
.It Fl m, Fl -portmap
|
||||
Enable portmapping via NAT-PMP or UPnP
|
||||
.It Fl M, Fl -no-portmap
|
||||
|
@ -94,10 +76,6 @@ Disable portmapping
|
|||
Create torrent from the specified file or directory
|
||||
.It Fl p, -port Ar port
|
||||
Set the port to listen for incoming peers. (Default: 51413)
|
||||
.It Fl r, Fl -private
|
||||
When creating a new torrent, set its 'private' flag
|
||||
.It Fl s, -scrape
|
||||
Print the current number of seeders and leechers for the specified torrent
|
||||
.It Fl t, -tos
|
||||
Set the peer socket TOS for local router-based traffic shaping.
|
||||
.It Fl u, -uplimit Ar number
|
||||
|
|
|
@ -476,6 +476,7 @@ AC_CONFIG_FILES([Makefile
|
|||
daemon/Makefile
|
||||
doc/Makefile
|
||||
libtransmission/Makefile
|
||||
utils/Makefile
|
||||
third-party/Makefile
|
||||
third-party/miniupnp/Makefile
|
||||
third-party/libnatpmp/Makefile
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* This file Copyright (C) 2008-2010 Mnemosyne LLC
|
||||
*
|
||||
* This file is licensed by the GPL version 2. Works owned by the
|
||||
* Transmission project are granted a special exemption to clause 2(b)
|
||||
* so that the bulk of its code can remain under the MIT license.
|
||||
* This exemption does not extend to derived works not owned by
|
||||
* the Transmission project.
|
||||
* 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$
|
||||
*/
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* This file Copyright (C) 2008-2010 Mnemosyne LLC
|
||||
*
|
||||
* This file is licensed by the GPL version 2. Works owned by the
|
||||
* Transmission project are granted a special exemption to clause 2(b)
|
||||
* so that the bulk of its code can remain under the MIT license.
|
||||
* This exemption does not extend to derived works not owned by
|
||||
* the Transmission project.
|
||||
* 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$
|
||||
*/
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* This file Copyright (C) 2009-2010 Mnemosyne LLC
|
||||
*
|
||||
* This file is licensed by the GPL version 2. Works owned by the
|
||||
* Transmission project are granted a special exemption to clause 2(b)
|
||||
* so that the bulk of its code can remain under the MIT license.
|
||||
* This exemption does not extend to derived works not owned by
|
||||
* the Transmission project.
|
||||
* 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$
|
||||
*/
|
||||
|
|
|
@ -423,6 +423,19 @@ tr_bencListChild( tr_benc * val,
|
|||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
tr_bencListRemove( tr_benc * list, size_t i )
|
||||
{
|
||||
if( tr_bencIsList( list ) && ( i < list->val.l.count ) )
|
||||
{
|
||||
tr_bencFree( &list->val.l.vals[i] );
|
||||
tr_removeElementFromArray( list->val.l.vals, i, sizeof( tr_benc ), list->val.l.count-- );
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
tr_benc_warning( const char * err )
|
||||
{
|
||||
|
|
|
@ -156,6 +156,8 @@ size_t tr_bencListSize( const tr_benc * list );
|
|||
|
||||
tr_benc * tr_bencListChild( tr_benc * list, size_t n );
|
||||
|
||||
int tr_bencListRemove( tr_benc *, size_t n );
|
||||
|
||||
/***
|
||||
****
|
||||
***/
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
*/
|
||||
|
||||
#ifndef __TRANSMISSION__
|
||||
#error only libtransmission should #include this header.
|
||||
#error only libtransmission should #include this header.
|
||||
#endif
|
||||
|
||||
#ifndef TR_LIST_H
|
||||
|
|
|
@ -114,11 +114,7 @@ tr_getLog( void )
|
|||
void
|
||||
tr_setMessageLevel( int level )
|
||||
{
|
||||
tr_lockLock( messageLock );
|
||||
|
||||
messageLevel = MAX( 0, level );
|
||||
|
||||
tr_lockUnlock( messageLock );
|
||||
}
|
||||
|
||||
int
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
AM_CPPFLAGS = -I@top_srcdir@
|
||||
|
||||
AM_CFLAGS = \
|
||||
@LIBCURL_CFLAGS@ \
|
||||
@OPENSSL_CFLAGS@ \
|
||||
@ZLIB_CFLAGS@ \
|
||||
@PTHREAD_CFLAGS@
|
||||
|
||||
AM_LDFLAGS = \
|
||||
@ZLIB_LDFLAGS@
|
||||
|
||||
bin_PROGRAMS = \
|
||||
transmission-create \
|
||||
transmission-edit \
|
||||
transmission-show
|
||||
|
||||
transmission_create_SOURCES = create.c
|
||||
transmission_edit_SOURCES = edit.c
|
||||
transmission_show_SOURCES = show.c
|
||||
|
||||
dist_man_MANS = \
|
||||
transmission-create.1 \
|
||||
transmission-edit.1
|
||||
|
||||
transmission_create_LDADD = \
|
||||
$(top_builddir)/libtransmission/libtransmission.a \
|
||||
$(top_builddir)/third-party/miniupnp/libminiupnp.a \
|
||||
$(top_builddir)/third-party/libnatpmp/libnatpmp.a \
|
||||
@DHT_LIBS@ \
|
||||
@LIBEVENT_LIBS@ \
|
||||
@LIBCURL_LIBS@ \
|
||||
@OPENSSL_LIBS@ \
|
||||
@ZLIB_LIBS@ \
|
||||
@PTHREAD_LIBS@
|
||||
transmission_edit_LDADD = $(transmission_create_LDADD)
|
||||
transmission_show_LDADD = $(transmission_create_LDADD)
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* This file Copyright (C) 2010 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>
|
||||
#include <unistd.h> /* getcwd() */
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/makemeta.h>
|
||||
#include <libtransmission/tr-getopt.h>
|
||||
#include <libtransmission/utils.h>
|
||||
|
||||
#define MY_NAME "transmission-create"
|
||||
|
||||
#define MAX_TRACKERS 128
|
||||
static tr_tracker_info trackers[MAX_TRACKERS];
|
||||
static int trackerCount = 0;
|
||||
static tr_bool isPrivate = FALSE;
|
||||
const char * comment = NULL;
|
||||
const char * outfile = NULL;
|
||||
const char * infile = NULL;
|
||||
|
||||
static tr_option options[] =
|
||||
{
|
||||
{ 'p', "private", "Allow this torrent to only be used with the specified tracker(s)", "p", 0, NULL },
|
||||
{ 'o', "outfile", "Save the generated .torrent to this filename", "o", 1, "<file>" },
|
||||
{ 'c', "comment", "Add a comment", "c", 1, "<comment>" },
|
||||
{ 't', "tracker", "Add a tracker's announce URL", "t", 1, "<url>" },
|
||||
{ 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const char *
|
||||
getUsage( void )
|
||||
{
|
||||
return "Usage: " MY_NAME " [options] <file|directory>";
|
||||
}
|
||||
|
||||
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 'p': isPrivate = TRUE; break;
|
||||
case 'o': outfile = optarg; break;
|
||||
case 'c': comment = optarg; break;
|
||||
case 't': if( trackerCount + 1 < MAX_TRACKERS ) {
|
||||
trackers[trackerCount].tier = trackerCount;
|
||||
trackers[trackerCount].announce = (char*) optarg;
|
||||
++trackerCount;
|
||||
}
|
||||
break;
|
||||
case TR_OPT_UNK: infile = optarg; break;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char*
|
||||
tr_getcwd( void )
|
||||
{
|
||||
char buf[2048];
|
||||
*buf = '\0';
|
||||
#ifdef WIN32
|
||||
_getcwd( buf, sizeof( buf ) );
|
||||
#else
|
||||
getcwd( buf, sizeof( buf ) );
|
||||
#endif
|
||||
return tr_strdup( buf );
|
||||
}
|
||||
|
||||
int
|
||||
main( int argc, char * argv[] )
|
||||
{
|
||||
char * out2 = NULL;
|
||||
tr_metainfo_builder * b = NULL;
|
||||
|
||||
tr_setMessageLevel( TR_MSG_ERR );
|
||||
|
||||
if( parseCommandLine( argc, (const char**)argv ) )
|
||||
return EXIT_FAILURE;
|
||||
|
||||
if( !infile )
|
||||
{
|
||||
fprintf( stderr, "ERROR: No input file or directory specified.\n" );
|
||||
tr_getopt_usage( MY_NAME, getUsage( ), options );
|
||||
fprintf( stderr, "\n" );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if( outfile == NULL )
|
||||
{
|
||||
char * base = tr_basename( infile );
|
||||
char * end = tr_strdup_printf( "%s.torrent", base );
|
||||
char * cwd = tr_getcwd( );
|
||||
outfile = out2 = tr_buildPath( cwd, end, NULL );
|
||||
tr_free( cwd );
|
||||
tr_free( end );
|
||||
tr_free( base );
|
||||
}
|
||||
|
||||
if( !trackerCount )
|
||||
{
|
||||
if( isPrivate )
|
||||
{
|
||||
fprintf( stderr, "ERROR: no trackers specified for a private torrent\n" );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf( "WARNING: no trackers specified\n" );
|
||||
}
|
||||
}
|
||||
|
||||
printf( "Creating torrent \"%s\" ...", outfile );
|
||||
fflush( stdout );
|
||||
|
||||
b = tr_metaInfoBuilderCreate( infile );
|
||||
tr_makeMetaInfo( b, outfile, trackers, trackerCount, comment, isPrivate );
|
||||
while( !b->isDone ) {
|
||||
tr_wait_msec( 500 );
|
||||
putc( '.', stdout );
|
||||
fflush( stdout );
|
||||
}
|
||||
|
||||
putc( ' ', stdout );
|
||||
switch( b->result ) {
|
||||
case TR_MAKEMETA_OK: printf( "done!" ); break;
|
||||
case TR_MAKEMETA_URL: printf( "bad announce URL: \"%s\"", b->errfile ); break;
|
||||
case TR_MAKEMETA_IO_READ: printf( "error reading \"%s\": %s", b->errfile, tr_strerror(b->my_errno) ); break;
|
||||
case TR_MAKEMETA_IO_WRITE: printf( "error writing \"%s\": %s", b->errfile, tr_strerror(b->my_errno) ); break;
|
||||
case TR_MAKEMETA_CANCELLED: printf( "cancelled" ); break;
|
||||
}
|
||||
putc( '\n', stdout );
|
||||
|
||||
tr_free( out2 );
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
* This file Copyright (C) 2010 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>
|
||||
|
||||
#include <event.h> /* evbuffer */
|
||||
|
||||
#include <libtransmission/transmission.h>
|
||||
#include <libtransmission/bencode.h>
|
||||
#include <libtransmission/tr-getopt.h>
|
||||
#include <libtransmission/utils.h>
|
||||
|
||||
#define MY_NAME "transmission-edit"
|
||||
|
||||
int fileCount = 0;
|
||||
const char ** files = NULL;
|
||||
const char * add = NULL;
|
||||
const char * deleteme = NULL;
|
||||
const char * replace[2] = { NULL, NULL };
|
||||
|
||||
static tr_option options[] =
|
||||
{
|
||||
{ 'a', "add", "Add a tracker's announce URL", "a", 1, "<url>" },
|
||||
{ 'd', "delete", "Delete a tracker's announce URL", "d", 1, "<url>" },
|
||||
{ 'r', "replace", "Search and replace a substring in the announce URLs", "r", 1, "<old> <new>" },
|
||||
{ 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const char *
|
||||
getUsage( void )
|
||||
{
|
||||
return "Usage: " MY_NAME " [options] torrent-file(s)";
|
||||
}
|
||||
|
||||
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 'a': add = optarg;
|
||||
break;
|
||||
case 'd': deleteme = optarg;
|
||||
break;
|
||||
case 'r': replace[0] = optarg;
|
||||
c = tr_getopt( getUsage( ), argc, argv, options, &optarg );
|
||||
if( c != TR_OPT_UNK ) return 1;
|
||||
replace[1] = optarg;
|
||||
break;
|
||||
case TR_OPT_UNK: files[fileCount++] = optarg; break;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static tr_bool
|
||||
removeURL( tr_benc * metainfo, const char * url )
|
||||
{
|
||||
const char * str;
|
||||
tr_benc * announce_list;
|
||||
tr_bool changed = FALSE;
|
||||
|
||||
if( tr_bencDictFindStr( metainfo, "announce", &str ) && !strcmp( str, url ) )
|
||||
{
|
||||
printf( "\tRemoved \"%s\" from \"announce\"\n", str );
|
||||
tr_bencDictRemove( metainfo, "announce" );
|
||||
changed = TRUE;
|
||||
}
|
||||
|
||||
if( tr_bencDictFindList( metainfo, "announce-list", &announce_list ) )
|
||||
{
|
||||
tr_benc * tier;
|
||||
int tierIndex = 0;
|
||||
while(( tier = tr_bencListChild( announce_list, tierIndex )))
|
||||
{
|
||||
tr_benc * node;
|
||||
int nodeIndex = 0;
|
||||
while(( node = tr_bencListChild( tier, nodeIndex )))
|
||||
{
|
||||
if( tr_bencGetStr( node, &str ) && !strcmp( str, url ) )
|
||||
{
|
||||
printf( "\tRemoved \"%s\" from \"announce-list\" tier #%d\n", str, (tierIndex+1) );
|
||||
tr_bencListRemove( tier, nodeIndex );
|
||||
changed = TRUE;
|
||||
}
|
||||
else ++nodeIndex;
|
||||
}
|
||||
|
||||
if( tr_bencListSize( tier ) == 0 )
|
||||
{
|
||||
printf( "\tNo URLs left in tier #%d... removing tier\n", (tierIndex+1) );
|
||||
tr_bencListRemove( announce_list, tierIndex );
|
||||
}
|
||||
else ++tierIndex;
|
||||
}
|
||||
|
||||
if( tr_bencListSize( announce_list ) == 0 )
|
||||
{
|
||||
printf( "\tNo tiers left... removing announce-list\n" );
|
||||
tr_bencDictRemove( metainfo, "announce-list" );
|
||||
}
|
||||
}
|
||||
|
||||
/* if we removed the "announce" field and there's still another track left,
|
||||
* use it as the "announce" field */
|
||||
if( changed && !tr_bencDictFindStr( metainfo, "announce", &str ) )
|
||||
{
|
||||
tr_benc * tier;
|
||||
tr_benc * node;
|
||||
|
||||
if(( tier = tr_bencListChild( announce_list, 0 ))) {
|
||||
if(( node = tr_bencListChild( tier, 0 ))) {
|
||||
if( tr_bencGetStr( node, &str ) ) {
|
||||
tr_bencDictAddStr( metainfo, "announce", str );
|
||||
printf( "\tAdded \"%s\" to announce\n", str );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static char*
|
||||
replaceSubstr( const char * str, const char * in, const char * out )
|
||||
{
|
||||
char * walk;
|
||||
struct evbuffer * buf = evbuffer_new( );
|
||||
const size_t inlen = strlen( in );
|
||||
const size_t outlen = strlen( out );
|
||||
|
||||
while(( walk = strstr( str, in )))
|
||||
{
|
||||
evbuffer_add( buf, str, walk-str );
|
||||
evbuffer_add( buf, out, outlen );
|
||||
str = walk + inlen;
|
||||
}
|
||||
|
||||
walk = tr_strndup( EVBUFFER_DATA( buf ), EVBUFFER_LENGTH( buf ) );
|
||||
evbuffer_free( buf );
|
||||
return walk;
|
||||
}
|
||||
|
||||
static tr_bool
|
||||
replaceURL( tr_benc * metainfo, const char * in, const char * out )
|
||||
{
|
||||
const char * str;
|
||||
tr_benc * announce_list;
|
||||
tr_bool changed = FALSE;
|
||||
|
||||
if( tr_bencDictFindStr( metainfo, "announce", &str ) && strstr( str, in ) )
|
||||
{
|
||||
char * newstr = replaceSubstr( str, in, out );
|
||||
printf( "\tReplaced in \"announce\": \"%s\" --> \"%s\"\n", str, newstr );
|
||||
tr_bencDictAddStr( metainfo, "announce", newstr );
|
||||
tr_free( newstr );
|
||||
changed = TRUE;
|
||||
}
|
||||
|
||||
if( tr_bencDictFindList( metainfo, "announce-list", &announce_list ) )
|
||||
{
|
||||
tr_benc * tier;
|
||||
int tierCount = 0;
|
||||
while(( tier = tr_bencListChild( announce_list, tierCount++ )))
|
||||
{
|
||||
tr_benc * node;
|
||||
int nodeCount = 0;
|
||||
while(( node = tr_bencListChild( tier, nodeCount++ )))
|
||||
{
|
||||
if( tr_bencGetStr( node, &str ) && strstr( str, in ) )
|
||||
{
|
||||
char * newstr = replaceSubstr( str, in, out );
|
||||
printf( "\tReplaced in \"announce-list\" tier %d: \"%s\" --> \"%s\"\n", tierCount, str, newstr );
|
||||
tr_bencFree( node );
|
||||
tr_bencInitStr( node, newstr, -1 );
|
||||
tr_free( newstr );
|
||||
changed = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
static tr_bool
|
||||
addURL( tr_benc * metainfo, const char * url )
|
||||
{
|
||||
const char * str;
|
||||
tr_benc * announce_list;
|
||||
tr_bool changed = FALSE;
|
||||
tr_bool match = FALSE;
|
||||
|
||||
/* maybe add it to "announce" */
|
||||
if( !tr_bencDictFindStr( metainfo, "announce", &str ) )
|
||||
{
|
||||
printf( "\tAdded \"%s\" in \"announce\"\n", url );
|
||||
tr_bencDictAddStr( metainfo, "announce", url );
|
||||
changed = TRUE;
|
||||
}
|
||||
|
||||
/* see if it's already in announce-list */
|
||||
if( tr_bencDictFindList( metainfo, "announce-list", &announce_list ) ) {
|
||||
tr_benc * tier;
|
||||
int tierCount = 0;
|
||||
while(( tier = tr_bencListChild( announce_list, tierCount++ ))) {
|
||||
tr_benc * node;
|
||||
int nodeCount = 0;
|
||||
while(( node = tr_bencListChild( tier, nodeCount++ )))
|
||||
if( tr_bencGetStr( node, &str ) && !strcmp( str, url ) )
|
||||
match = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* if it's not in announce-list, add it now */
|
||||
if( !match )
|
||||
{
|
||||
tr_benc * tier;
|
||||
|
||||
if( !tr_bencDictFindList( metainfo, "announce-list", &announce_list ) )
|
||||
announce_list = tr_bencDictAddList( metainfo, "announce-list", 1 );
|
||||
|
||||
tier = tr_bencListAddList( announce_list, 1 );
|
||||
tr_bencListAddStr( tier, url );
|
||||
printf( "\tAdded \"%s\" to \"announce-list\" tier %d\n", url, tr_bencListSize( announce_list ) );
|
||||
changed = TRUE;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
int
|
||||
main( int argc, char * argv[] )
|
||||
{
|
||||
int i;
|
||||
int changedCount = 0;
|
||||
|
||||
files = tr_new0( const char*, argc );
|
||||
|
||||
tr_setMessageLevel( TR_MSG_ERR );
|
||||
|
||||
if( parseCommandLine( argc, (const char**)argv ) )
|
||||
return EXIT_FAILURE;
|
||||
|
||||
if( fileCount < 1 )
|
||||
{
|
||||
fprintf( stderr, "ERROR: No torrent files specified.\n" );
|
||||
tr_getopt_usage( MY_NAME, getUsage( ), options );
|
||||
fprintf( stderr, "\n" );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if( !add && !deleteme && !replace[0] )
|
||||
{
|
||||
fprintf( stderr, "ERROR: Must specify -a, -d or -r\n" );
|
||||
tr_getopt_usage( MY_NAME, getUsage( ), options );
|
||||
fprintf( stderr, "\n" );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
for( i=0; i<fileCount; ++i )
|
||||
{
|
||||
tr_benc top;
|
||||
tr_bool changed = FALSE;
|
||||
const char * filename = files[i];
|
||||
|
||||
printf( "%s\n", filename );
|
||||
|
||||
if( tr_bencLoadFile( &top, TR_FMT_BENC, filename ) )
|
||||
{
|
||||
printf( "\tError reading file\n" );
|
||||
continue;
|
||||
}
|
||||
|
||||
if( deleteme != NULL )
|
||||
changed |= removeURL( &top, deleteme );
|
||||
|
||||
if( add != NULL )
|
||||
changed = addURL( &top, add );
|
||||
|
||||
if( replace[0] && replace[1] )
|
||||
changed |= replaceURL( &top, replace[0], replace[1] );
|
||||
|
||||
if( changed )
|
||||
{
|
||||
++changedCount;
|
||||
tr_bencToFile( &top, TR_FMT_BENC, filename );
|
||||
}
|
||||
|
||||
tr_bencFree( &top );
|
||||
}
|
||||
|
||||
printf( "Changed %d files\n", changedCount );
|
||||
|
||||
tr_free( files );
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,298 @@
|
|||
/*
|
||||
* This file Copyright (C) 2010 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>
|
||||
#include <time.h>
|
||||
|
||||
#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <event.h> /* struct evbuffer */
|
||||
|
||||
#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
|
||||
|
||||
static tr_option options[] =
|
||||
{
|
||||
{ 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", 0, NULL },
|
||||
{ 0, NULL, NULL, NULL, 0, NULL }
|
||||
};
|
||||
|
||||
static const char *
|
||||
getUsage( void )
|
||||
{
|
||||
return "Usage: " MY_NAME " [options] <.torrent file>";
|
||||
}
|
||||
|
||||
static tr_bool scrapeFlag = 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 TR_OPT_UNK: filename = optarg; break;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const double KiB = 1024.0;
|
||||
static const double MiB = 1024.0 * 1024.0;
|
||||
static const double GiB = 1024.0 * 1024.0 * 1024.0;
|
||||
|
||||
static char*
|
||||
strlsize( char * buf, int64_t size, size_t buflen )
|
||||
{
|
||||
if( !size )
|
||||
tr_strlcpy( buf, "None", buflen );
|
||||
else if( size < (int64_t)KiB )
|
||||
tr_snprintf( buf, buflen, "%'" PRId64 " bytes", (int64_t)size );
|
||||
else
|
||||
{
|
||||
double displayed_size;
|
||||
if( size < (int64_t)MiB )
|
||||
{
|
||||
displayed_size = (double) size / KiB;
|
||||
tr_snprintf( buf, buflen, "%'.1f KiB", displayed_size );
|
||||
}
|
||||
else if( size < (int64_t)GiB )
|
||||
{
|
||||
displayed_size = (double) size / MiB;
|
||||
tr_snprintf( buf, buflen, "%'.1f MiB", displayed_size );
|
||||
}
|
||||
else
|
||||
{
|
||||
displayed_size = (double) size / GiB;
|
||||
tr_snprintf( buf, buflen, "%'.1f GiB", displayed_size );
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void
|
||||
showInfo( const tr_info * inf )
|
||||
{
|
||||
int i;
|
||||
char buf[128];
|
||||
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", strlsize( buf, inf->pieceSize, sizeof( buf ) ) );
|
||||
printf( " Total Size: %s\n", strlsize( buf, inf->totalSize, sizeof( buf ) ) );
|
||||
printf( " Privacy: %s\n", inf->isPrivate ? "Private torrent" : "Public torrent" );
|
||||
|
||||
/**
|
||||
*** Trackers
|
||||
**/
|
||||
|
||||
printf( "\nTRACKERS\n" );
|
||||
for( i=0; i<(int)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 );
|
||||
}
|
||||
|
||||
/**
|
||||
*** Files
|
||||
**/
|
||||
|
||||
printf( "\nFILES\n\n" );
|
||||
for( i=0; i<(int)inf->fileCount; ++i )
|
||||
printf( " %s (%s)\n", inf->files[i].name, strlsize( buf, inf->files[i].length, sizeof( buf ) ) );
|
||||
}
|
||||
|
||||
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 * url = inf->trackers[i].scrape;
|
||||
|
||||
if( url == NULL )
|
||||
continue;
|
||||
|
||||
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;
|
||||
tr_bool matched = FALSE;
|
||||
const char * begin = (const char*) EVBUFFER_DATA( buf );
|
||||
const char * end = begin + EVBUFFER_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" );
|
||||
}
|
||||
}
|
||||
|
||||
evbuffer_free( buf );
|
||||
curl_easy_cleanup( curl );
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
main( int argc, char * argv[] )
|
||||
{
|
||||
int err;
|
||||
tr_info inf;
|
||||
tr_ctor * ctor;
|
||||
|
||||
tr_setMessageLevel( TR_MSG_ERR );
|
||||
|
||||
if( parseCommandLine( argc, (const char**)argv ) )
|
||||
return EXIT_FAILURE;
|
||||
|
||||
/* 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;
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
.Dd June 9, 2010
|
||||
.Dt TRANSMISSION-CREATE 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm transmission-create
|
||||
.Nd command-line utility to create .torrent files
|
||||
.Sh SYNOPSIS
|
||||
.Bk -words
|
||||
.Nm
|
||||
.Op Fl h
|
||||
.Op Fl p
|
||||
.Op Fl o Ar file
|
||||
.Op Fl c Ar comment
|
||||
.Op Fl t Ar tracker
|
||||
.Op Ar source file or directory
|
||||
.Ek
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
creates BitTorrent .torrent files from the command line
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width Ds
|
||||
.It Fl h Fl -help
|
||||
Show a short help page and exit.
|
||||
.It Fl p Fl -private
|
||||
Flag the torrent as intended for use on private trackers.
|
||||
.It Fl c Fl -comment
|
||||
Add a comment to the torrent file.
|
||||
.It Fl t Fl -tracker
|
||||
Add a tracker's
|
||||
.Ar announce URL
|
||||
to the .torrent. Most torrents will have at least one
|
||||
.Ar announce URL.
|
||||
To add more than one, use this option multiple times.
|
||||
|
||||
.Sh AUTHORS
|
||||
.An -nosplit
|
||||
.An Charles Kerr
|
||||
.Sh SEE ALSO
|
||||
.Xr transmission-daemon 1 ,
|
||||
.Xr transmission-gtk 1 ,
|
||||
.Xr transmission-edit 1 ,
|
||||
.Xr transmission-show 1
|
||||
.Pp
|
||||
http://www.transmissionbt.com/
|
|
@ -0,0 +1,43 @@
|
|||
.Dd June 9, 2010
|
||||
.Dt TRANSMISSION-EDIT 1
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm transmission-edit
|
||||
.Nd command-line utility to modify .torrent files' announce URLs
|
||||
.Sh SYNOPSIS
|
||||
.Bk -words
|
||||
.Nm
|
||||
.Op Fl h
|
||||
.Op Fl a Ar url
|
||||
.Op Fl d Ar url
|
||||
.Op Fl r Ar search Ar replace
|
||||
.Ar torrentfile(s)
|
||||
.Ek
|
||||
.Sh DESCRIPTION
|
||||
.Nm
|
||||
command-line utility to modify .torrent files' announce URLs
|
||||
.Sh OPTIONS
|
||||
.Bl -tag -width Ds
|
||||
.It Fl h Fl -help
|
||||
Show a short help page and exit.
|
||||
.It Fl a Fl -add Ar URL
|
||||
Add an announce URL to the torrent's announce-list if it's not already in the list
|
||||
.It Fl d Fl -delete Ar URL
|
||||
Remove an announce URL from the torrent's announce-list
|
||||
.It Fl r Fl -replace Ar search Ar replace
|
||||
Substring search-and-replace inside a torrent's announce URLs. This can be used to change an announce URL when the tracker moves or your passcode changes.
|
||||
.Sh EXAMPLES
|
||||
Update a tracker passcode in all your torrents:
|
||||
.Bd -literal -offset indent
|
||||
$ transmission-edit -r old-passcode new-passcode ~/.config/transmission/torrents/*\\.torrent
|
||||
.Ed
|
||||
.Sh AUTHORS
|
||||
.An -nosplit
|
||||
.An Charles Kerr
|
||||
.Sh SEE ALSO
|
||||
.Xr transmission-daemon 1 ,
|
||||
.Xr transmission-gtk 1,
|
||||
.Xr transmission-create 1 ,
|
||||
.Xr transmission-show 1
|
||||
.Pp
|
||||
http://www.transmissionbt.com/
|
Loading…
Reference in New Issue