/****************************************************************************** * $Id$ * * Copyright (c) 2005-2006 Transmission authors and contributors * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. *****************************************************************************/ #include #include #include #include #include #include #include #include #include /* tr_metainfoFree */ #include #include /* tr_wait */ #include /* tr_webRun */ #define MY_NAME "transmission-cli" static int showInfo = 0; static int showScrape = 0; static int isPrivate = 0; static int verboseLevel = 0; static int peerPort = TR_DEFAULT_PORT; static int peerSocketTOS = TR_DEFAULT_PEER_SOCKET_TOS; static int uploadLimit = 20; static int downloadLimit = -1; static int natTraversal = 0; static int verify = 0; static sig_atomic_t gotsig = 0; static sig_atomic_t manualUpdate = 0; static const char * torrentPath = NULL; static const char * downloadDir = NULL; static const char * finishCall = NULL; static const char * announce = NULL; static const char * configdir = NULL; static const char * sourceFile = NULL; static const char * comment = NULL; static int parseCommandLine ( int argc, const char ** argv ); static void sigHandler ( int signal ); static char * getStringRatio( float ratio ) { static char string[20]; if( ratio == TR_RATIO_NA ) return "n/a"; snprintf( string, sizeof string, "%.3f", ratio ); return string; } 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 += snprintf( out, 4, "%%%02X", (unsigned int)*in++ ); *out = '\0'; } #define LINEWIDTH 80 static void torrentStateChanged( tr_torrent * torrent UNUSED, cp_status_t status UNUSED, void * user_data UNUSED ) { system( finishCall ); } static int leftToScrape = 0; static void scrapeDoneFunc( struct tr_handle * 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 printf( "unable to parse response (http code %lu) at %s", response_code, (char*)host ); --leftToScrape; } int main( int argc, char ** argv ) { int i, error; tr_handle * h; tr_ctor * ctor; tr_torrent * tor = NULL; char cwd[MAX_PATH_LENGTH]; printf( "Transmission %s - http://www.transmissionbt.com/\n", LONG_VERSION_STRING ); /* Get options */ if( parseCommandLine( argc, (const char**)argv ) ) return EXIT_FAILURE; /* Check the options for validity */ if( !torrentPath ) { printf( "No torrent specified!\n" ); return EXIT_FAILURE; } if( peerPort < 1 || peerPort > 65535 ) { printf( "Invalid port '%d'\n", peerPort ); return EXIT_FAILURE; } if( peerSocketTOS < 0 || peerSocketTOS > 255 ) { printf( "Invalid TOS '%d'\n", peerSocketTOS ); 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 ) ) peerPort = -1; if( configdir == NULL ) configdir = tr_getDefaultConfigDir( ); /* if no download directory specified, use cwd instead */ if( !downloadDir ) { getcwd( cwd, sizeof( cwd ) ); downloadDir = cwd; } /* Initialize libtransmission */ h = tr_sessionInitFull( configdir, "cli", /* tag */ downloadDir, /* where to download torrents */ TR_DEFAULT_PEX_ENABLED, natTraversal, /* nat enabled */ peerPort, TR_ENCRYPTION_PREFERRED, uploadLimit >= 0, uploadLimit, downloadLimit >= 0, downloadLimit, TR_DEFAULT_GLOBAL_PEER_LIMIT, verboseLevel + 1, /* messageLevel */ 0, /* is message queueing enabled? */ TR_DEFAULT_BLOCKLIST_ENABLED, peerSocketTOS, TR_DEFAULT_RPC_ENABLED, TR_DEFAULT_RPC_PORT, TR_DEFAULT_RPC_ACL, FALSE, "fnord", "potzrebie", TR_DEFAULT_PROXY_ENABLED, TR_DEFAULT_PROXY, TR_DEFAULT_PROXY_TYPE, TR_DEFAULT_PROXY_AUTH_ENABLED, TR_DEFAULT_PROXY_USERNAME, TR_DEFAULT_PROXY_PASSWORD ); if( sourceFile && *sourceFile ) /* creating a torrent */ { int err; tr_metainfo_builder * builder = tr_metaInfoBuilderCreate( h, sourceFile ); tr_tracker_info ti; ti.tier = 0; ti.announce = (char*) announce; tr_makeMetaInfo( builder, torrentPath, &ti, 1, comment, isPrivate ); while( !builder->isDone ) { tr_wait( 1000 ); printf( "." ); } err = builder->result; tr_metaInfoBuilderFree( builder ); return err; } ctor = tr_ctorNew( h ); tr_ctorSetMetainfoFromFile( ctor, torrentPath ); tr_ctorSetPaused( ctor, TR_FORCE, showScrape ); tr_ctorSetDownloadDir( ctor, TR_FORCE, downloadDir ); if( showScrape ) { tr_info info; if( !tr_torrentParse( h, ctor, &info ) ) { int i; const time_t start = time( NULL ); for( i=0; i0 && ((time(NULL)-start)<20) ) tr_wait( 250 ); } goto cleanup; } if( showInfo ) { tr_info info; if( !tr_torrentParse( h, ctor, &info ) ) { int prevTier = -1; tr_file_index_t ff; printf( "hash:\t" ); for( i=0; istatus & TR_STATUS_CHECK_WAIT ) { chars = snprintf( string, sizeof string, "Waiting to verify local files..." ); } else if( s->status & TR_STATUS_CHECK ) { chars = snprintf( string, sizeof string, "Verifying local files... %.2f%%, found %.2f%% valid", 100 * s->recheckProgress, 100.0 * s->percentDone ); } else if( s->status & TR_STATUS_DOWNLOAD ) { chars = snprintf( string, sizeof string, "Progress: %.2f %%, %d peer%s, dl from %d (%.2f KB/s), " "ul to %d (%.2f KB/s) [%s]", 100.0 * s->percentDone, s->peersConnected, ( s->peersConnected == 1 ) ? "" : "s", s->peersSendingToUs, s->rateDownload, s->peersGettingFromUs, s->rateUpload, getStringRatio(s->ratio) ); } else if( s->status & TR_STATUS_SEED ) { chars = snprintf( string, sizeof string, "Seeding, uploading to %d of %d peer(s), %.2f KB/s [%s]", s->peersGettingFromUs, s->peersConnected, s->rateUpload, getStringRatio(s->ratio) ); } else if( s->status & TR_STATUS_STOPPED ) { break; } if( ( signed )sizeof string > chars ) { memset( &string[chars], ' ', sizeof string - 1 - chars ); } string[sizeof string - 1] = '\0'; fprintf( stderr, "\r%s", string ); if( s->error ) { fprintf( stderr, "\n%s\n", s->errorString ); } else if( verboseLevel > 0 ) { fprintf( stderr, "\n" ); } } fprintf( stderr, "\n" ); /* try for 5 seconds to delete any port mappings for nat traversal */ tr_sessionSetPortForwardingEnabled( h, 0 ); for( i=0; i<10; ++i ) { const tr_port_forwarding f = tr_sessionGetPortForwarding( h ); if( f == TR_PORT_UNMAPPED ) break; tr_wait( 500 ); } cleanup: tr_sessionClose( h ); return EXIT_SUCCESS; } /*** **** **** **** ***/ static const char * getUsage( void ) { return "A fast and easy BitTorrent client\n" "\n" "Usage: "MY_NAME" [options] "; } const struct tr_option options[] = { { 'a', "announce", "When creating a new torrent, set its announce URL", "a", 1, "" }, { 'c', "comment", "When creating a new torrent, set its comment field", "c", 1, "" }, { 'd', "downlimit", "Set the maxiumum download speed in KB/s", "d", 1, "" }, { 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL }, { 'f', "finish", "Set a script to run when the torrent finishes", "f", 1, "