(trunk) #2983: add command-line utilities for creating .torrent files, for editing passkeys, for adding/removing trackers, etc.

This commit is contained in:
Charles Kerr
2010-06-16 14:27:24 +00:00
parent 71c1919cfc
commit eda211e5ce
17 changed files with 920 additions and 260 deletions

View File

@@ -19,6 +19,7 @@ SUBDIRS = \
doc \ doc \
third-party \ third-party \
libtransmission \ libtransmission \
utils \
$(DAEMON_DIR) \ $(DAEMON_DIR) \
$(CLI_DIR) \ $(CLI_DIR) \
$(GTK_DIR) \ $(GTK_DIR) \

219
cli/cli.c
View File

@@ -30,7 +30,6 @@
#include <libtransmission/transmission.h> #include <libtransmission/transmission.h>
#include <libtransmission/bencode.h> #include <libtransmission/bencode.h>
#include <libtransmission/makemeta.h>
#include <libtransmission/tr-getopt.h> #include <libtransmission/tr-getopt.h>
#include <libtransmission/utils.h> /* tr_wait_msec */ #include <libtransmission/utils.h> /* tr_wait_msec */
#include <libtransmission/version.h> #include <libtransmission/version.h>
@@ -39,27 +38,16 @@
#define LINEWIDTH 80 #define LINEWIDTH 80
#define MY_NAME "transmissioncli" #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 tr_bool verify = 0;
static sig_atomic_t gotsig = 0; static sig_atomic_t gotsig = 0;
static sig_atomic_t manualUpdate = 0; static sig_atomic_t manualUpdate = 0;
static const char * torrentPath = NULL; 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[] = 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', "blocklist", "Enable peer blocklists", "b", 0, NULL },
{ 'B', "no-blocklist", "Disable 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', "downlimit", "Set max download speed in KiB/s", "d", 1, "<speed>" },
{ 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL }, { 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL },
{ 910, "encryption-required", "Encrypt all peer connections", "er", 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 }, { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
{ 'f', "finish", "Run a script when the torrent finishes", "f", 1, "<script>" }, { 'f', "finish", "Run a script when the torrent finishes", "f", 1, "<script>" },
{ 'g', "config-dir", "Where to find configuration files", "g", 1, "<path>" }, { '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', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
{ 'M', "no-portmap", "Disable portmapping", "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>" }, { '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>" }, { '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', "uplimit", "Set max upload speed in KiB/s", "u", 1, "<speed>" },
{ 'U', "no-uplimit", "Don't limit the upload speed", "U", 0, NULL }, { 'U', "no-uplimit", "Don't limit the upload speed", "U", 0, NULL },
@@ -112,30 +96,6 @@ tr_strlratio( char * buf,
return 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 tr_bool waitingOnWeb;
static void static void
@@ -149,83 +109,6 @@ onTorrentFileDownloaded( tr_session * session UNUSED,
waitingOnWeb = FALSE; 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 static void
getStatusStr( const tr_stat * st, getStatusStr( const tr_stat * st,
char * buf, char * buf,
@@ -304,8 +187,6 @@ main( int argc,
tr_torrent * tor = NULL; tr_torrent * tor = NULL;
tr_benc settings; tr_benc settings;
const char * configDir; const char * configDir;
tr_bool haveSource;
tr_bool haveAnnounce;
uint8_t * fileContents; uint8_t * fileContents;
size_t fileLength; size_t fileLength;
@@ -333,42 +214,12 @@ main( int argc,
return EXIT_FAILURE; 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 ); 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 ); ctor = tr_ctorNew( h );
fileContents = tr_loadFile( torrentPath, &fileLength ); fileContents = tr_loadFile( torrentPath, &fileLength );
tr_ctorSetPaused( ctor, TR_FORCE, showScrape ); tr_ctorSetPaused( ctor, TR_FORCE, FALSE );
if( fileContents != NULL ) { if( fileContents != NULL ) {
tr_ctorSetMetainfo( ctor, fileContents, fileLength ); tr_ctorSetMetainfo( ctor, fileContents, fileLength );
} else if( !memcmp( torrentPath, "magnet:?", 8 ) ) { } else if( !memcmp( torrentPath, "magnet:?", 8 ) ) {
@@ -380,57 +231,6 @@ main( int argc,
} }
tr_free( fileContents ); 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 ); tor = tr_torrentNew( ctor, &error );
tr_ctorFree( ctor ); tr_ctorFree( ctor );
if( !tor ) if( !tor )
@@ -495,8 +295,6 @@ main( int argc,
fprintf( stderr, "\n%s: %s\n", messageName[st->error], st->errorString ); fprintf( stderr, "\n%s: %s\n", messageName[st->error], st->errorString );
} }
cleanup:
tr_sessionSaveSettings( h, configDir, &settings ); tr_sessionSaveSettings( h, configDir, &settings );
printf( "\n" ); printf( "\n" );
@@ -521,18 +319,10 @@ parseCommandLine( tr_benc * d, int argc, const char ** argv )
{ {
switch( c ) 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 ); case 'b': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, TRUE );
break; break;
case 'B': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE ); case 'B': tr_bencDictAddBool( d, TR_PREFS_KEY_BLOCKLIST_ENABLED, FALSE );
break; break;
case 'c': comment = optarg;
break;
case 'd': tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED, atoi( optarg ) ); case 'd': tr_bencDictAddInt ( d, TR_PREFS_KEY_DSPEED, atoi( optarg ) );
tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, TRUE ); tr_bencDictAddBool( d, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
break; break;
@@ -543,19 +333,12 @@ parseCommandLine( tr_benc * d, int argc, const char ** argv )
break; break;
case 'g': /* handled above */ case 'g': /* handled above */
break; break;
case 'i': showInfo = 1;
break;
case 'm': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, TRUE ); case 'm': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
break; break;
case 'M': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, FALSE ); case 'M': tr_bencDictAddBool( d, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
break; break;
case 'n': sourceFile = optarg; break;
case 'p': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) ); case 'p': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_PORT, atoi( optarg ) );
break; break;
case 'r': isPrivate = 1;
break;
case 's': showScrape = 1;
break;
case 't': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_SOCKET_TOS, atoi( optarg ) ); case 't': tr_bencDictAddInt( d, TR_PREFS_KEY_PEER_SOCKET_TOS, atoi( optarg ) );
break; break;
case 'u': tr_bencDictAddInt( d, TR_PREFS_KEY_USPEED, atoi( optarg ) ); case 'u': tr_bencDictAddInt( d, TR_PREFS_KEY_USPEED, atoi( optarg ) );

View File

@@ -24,18 +24,6 @@
.Bk -words .Bk -words
.Fl h .Fl h
.Nm .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 b | B
.Op Fl d Ar number | Fl D .Op Fl d Ar number | Fl D
.Op Fl er | ep | et .Op Fl er | ep | et
@@ -58,15 +46,11 @@ scripting capabilities.
.Pp .Pp
The options are as follows: The options are as follows:
.Bl -tag -width Ds .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 .It Fl b Fl -blocklist
Enable peer blocklists. Transmission understands the bluetack blocklist file format. 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. New blocklists can be added by copying them into the config-dir's "blocklists" subdirectory.
.It Fl B Fl -no-blocklist .It Fl B Fl -no-blocklist
Disble blocklists. 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 .It Fl d, -downlimit Ar number
Set the maximum download speed in KB/s Set the maximum download speed in KB/s
.It Fl D, -no-downlimit .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. See http://trac.transmissionbt.com/wiki/ConfigFiles for more information.
.It Fl h, Fl -help .It Fl h, Fl -help
Prints a short usage summary. Prints a short usage summary.
.It Fl i, Fl -info
Shows torrent details and exit
.It Fl m, Fl -portmap .It Fl m, Fl -portmap
Enable portmapping via NAT-PMP or UPnP Enable portmapping via NAT-PMP or UPnP
.It Fl M, Fl -no-portmap .It Fl M, Fl -no-portmap
@@ -94,10 +76,6 @@ Disable portmapping
Create torrent from the specified file or directory Create torrent from the specified file or directory
.It Fl p, -port Ar port .It Fl p, -port Ar port
Set the port to listen for incoming peers. (Default: 51413) 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 .It Fl t, -tos
Set the peer socket TOS for local router-based traffic shaping. Set the peer socket TOS for local router-based traffic shaping.
.It Fl u, -uplimit Ar number .It Fl u, -uplimit Ar number

View File

@@ -476,6 +476,7 @@ AC_CONFIG_FILES([Makefile
daemon/Makefile daemon/Makefile
doc/Makefile doc/Makefile
libtransmission/Makefile libtransmission/Makefile
utils/Makefile
third-party/Makefile third-party/Makefile
third-party/miniupnp/Makefile third-party/miniupnp/Makefile
third-party/libnatpmp/Makefile third-party/libnatpmp/Makefile

View File

@@ -1,11 +1,11 @@
/* /*
* This file Copyright (C) 2008-2010 Mnemosyne LLC * This file Copyright (C) 2008-2010 Mnemosyne LLC
* *
* This file is licensed by the GPL version 2. Works owned by the * This program is free software; you can redistribute it and/or modify
* Transmission project are granted a special exemption to clause 2(b) * it under the terms of the GNU General Public License version 2
* so that the bulk of its code can remain under the MIT license. * as published by the Free Software Foundation.
* This exemption does not extend to derived works not owned by *
* the Transmission project. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* *
* $Id$ * $Id$
*/ */

View File

@@ -1,11 +1,11 @@
/* /*
* This file Copyright (C) 2008-2010 Mnemosyne LLC * This file Copyright (C) 2008-2010 Mnemosyne LLC
* *
* This file is licensed by the GPL version 2. Works owned by the * This program is free software; you can redistribute it and/or modify
* Transmission project are granted a special exemption to clause 2(b) * it under the terms of the GNU General Public License version 2
* so that the bulk of its code can remain under the MIT license. * as published by the Free Software Foundation.
* This exemption does not extend to derived works not owned by *
* the Transmission project. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* *
* $Id$ * $Id$
*/ */

View File

@@ -1,11 +1,11 @@
/* /*
* This file Copyright (C) 2009-2010 Mnemosyne LLC * This file Copyright (C) 2009-2010 Mnemosyne LLC
* *
* This file is licensed by the GPL version 2. Works owned by the * This program is free software; you can redistribute it and/or modify
* Transmission project are granted a special exemption to clause 2(b) * it under the terms of the GNU General Public License version 2
* so that the bulk of its code can remain under the MIT license. * as published by the Free Software Foundation.
* This exemption does not extend to derived works not owned by *
* the Transmission project. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* *
* $Id$ * $Id$
*/ */

View File

@@ -423,6 +423,19 @@ tr_bencListChild( tr_benc * val,
return ret; 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 static void
tr_benc_warning( const char * err ) tr_benc_warning( const char * err )
{ {

View File

@@ -156,6 +156,8 @@ size_t tr_bencListSize( const tr_benc * list );
tr_benc * tr_bencListChild( tr_benc * list, size_t n ); tr_benc * tr_bencListChild( tr_benc * list, size_t n );
int tr_bencListRemove( tr_benc *, size_t n );
/*** /***
**** ****
***/ ***/

View File

@@ -114,11 +114,7 @@ tr_getLog( void )
void void
tr_setMessageLevel( int level ) tr_setMessageLevel( int level )
{ {
tr_lockLock( messageLock );
messageLevel = MAX( 0, level ); messageLevel = MAX( 0, level );
tr_lockUnlock( messageLock );
} }
int int

36
utils/Makefile.am Normal file
View File

@@ -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)

152
utils/create.c Normal file
View File

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

313
utils/edit.c Normal file
View File

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

298
utils/show.c Normal file
View File

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

View File

@@ -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/

43
utils/transmission-edit.1 Normal file
View File

@@ -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/