/* * This file Copyright (C) 2008 Charles Kerr * * 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. * * $Id$ */ #include #include #include #include /* strcmp */ #include /* getcwd */ #include #include #include #include #include #include #include #include #include #define MY_NAME "transmission-remote" #define DEFAULT_HOST "localhost" #define DEFAULT_PORT TR_DEFAULT_RPC_PORT enum { TAG_LIST, TAG_DETAILS, TAG_FILES }; static const char* getUsage( void ) { return "Transmission "LONG_VERSION_STRING" http://www.transmissionbt.com/\n" "A fast and easy BitTorrent client\n" "\n" "Usage: "MY_NAME" [host] [options]\n" " "MY_NAME" [port] [options]\n" " "MY_NAME" [host:port] [options]\n" "\n" "See the man page for detailed explanations and many examples."; } static tr_option opts[] = { { 'a', "add", "Add torrent files", "a", 0, NULL }, { 'b', "debug", "Print debugging information", "b", 0, NULL }, { 'd', "downlimit", "Set the maximum download speed in KB/s", "d", 1, "" }, { 'D', "no-downlimit", "Don't limit the download speed", "D", 0, NULL }, { 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL }, { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL }, { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL }, { 'f', "files", "List the current torrent's files", "f", 0, NULL }, { 'g', "get", "Mark files for download", "g", 1, "" }, { 'G', "no-get", "Mark files for not downloading", "G", 1, "" }, { 'i', "info", "Show details of the current torrent(s)", "i", 0, NULL }, { 'l', "list", "List all torrents", "l", 0, NULL }, { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL }, { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL }, { 'n', "auth", "Set username for authentication", "n", 1, "" }, { 'p', "port", "Port for incoming peers (Default: "TR_DEFAULT_PORT_STR")", "p", 1, "" }, { 900, "priority-high", "Set the files' priorities as high", "ph", 1, "" }, { 901, "priority-normal", "Set the files' priorities as normal", "pn", 1, "" }, { 902, "priority-low", "Set the files' priorities as low", "pl", 1, "" }, { 'r', "remove", "Remove the current torrent(s)", "r", 0, NULL }, { 's', "start", "Start the current torrent(s)", "s", 0, NULL }, { 'S', "stop", "Stop the current torrent(s)", "S", 0, NULL }, { 't', "torrent", "Set the current torrent(s)", "t", 1, "" }, { 'u', "uplimit", "Set the maximum upload speed in KB/s", "u", 1, "" }, { 'U', "no-uplimit", "Don't limit the upload speed", "U", 0, NULL }, { 'v', "verify", "Verify the current torrent(s)", "v", 0, NULL }, { 'w', "download-dir", "Set the default download folder", "w", 1, "" }, { 'x', "pex", "Enable peer exchange (PEX)", "x", 0, NULL }, { 'X', "no-pex", "Disable peer exchange (PEX)", "X", 0, NULL }, { 0, NULL, NULL, NULL, 0, NULL } }; static void showUsage( void ) { tr_getopt_usage( MY_NAME, getUsage(), opts ); exit( 0 ); } static int numarg( const char * arg ) { char * end = NULL; const long num = strtol( arg, &end, 10 ); if( *end ) { fprintf( stderr, "Not a number: \"%s\"\n", arg ); showUsage( ); } return num; } static char * reqs[256]; /* arbitrary max */ static int reqCount = 0; static int debug = 0; static char * auth = NULL; static char* absolutify( char * buf, size_t len, const char * path ) { if( *path == '/' ) tr_strlcpy( buf, path, len ); else { char cwd[MAX_PATH_LENGTH]; getcwd( cwd, sizeof( cwd ) ); tr_buildPath( buf, len, cwd, path, NULL ); } return buf; } static char* getEncodedMetainfo( const char * filename ) { size_t len = 0; uint8_t * buf = tr_loadFile( filename, &len ); char * b64 = tr_base64_encode( buf, len, NULL ); tr_free( buf ); return b64; } static void addIdArg( tr_benc * args, const char * id ) { if( !*id ) { fprintf( stderr, "No torrent specified! Please use the -t option first.\n" ); id = "-1"; /* no torrent will have this ID, so should be a no-op */ } if( strcmp( id, "all" ) ) { tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen(id) ); } } static void addFiles( tr_benc * args, const char * key, const char * arg ) { tr_benc * files = tr_bencDictAddList( args, key, 100 ); if( !*arg ) { fprintf( stderr, "No files specified!\n" ); arg = "-1"; /* no file will have this index, so should be a no-op */ } if( strcmp( arg, "all" ) ) { const char * walk = arg; while( *walk ) { char * p; unsigned long l; errno = 0; l = strtol( walk, &p, 10 ); if( errno ) break; tr_bencListAddInt( files, l - 1 ); if( *p != ',' ) break; walk = p + 1; } } } #define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) ) static const char * files_keys[] = { "files", "name", "priorities", "wanted" }; static const char * details_keys[] = { "activityDate", "addedDate", "announceResponse", "announceURL", "comment", "corruptEver", "creator", "dateCreated", "doneDate", "downloadedEver", "errorString", "eta", "hashString", "haveUnchecked", "haveValid", "id", "isPrivate", "lastAnnounceTime", "lastScrapeTime", "leechers", "leftUntilDone", "name", "nextAnnounceTime", "nextScrapeTime", "pieceCount", "pieceSize", "rateDownload", "rateUpload", "recheckProgress", "scrapeResponse", "seeders", "sizeWhenDone", "sizeWhenDone", "startDate", "status", "timesCompleted", "totalSize", "uploadedEver" }; static const char * list_keys[] = { "downloadedEver", "eta", "id", "leftUntilDone", "name", "rateDownload", "rateUpload", "sizeWhenDone", "status", "uploadedEver" }; static void readargs( int argc, const char ** argv ) { int c; int addingTorrents = 0; const char * optarg; char id[4096]; *id = '\0'; while(( c = tr_getopt( getUsage(), argc, argv, opts, &optarg ))) { int i, n; char buf[MAX_PATH_LENGTH]; int addArg = TRUE; tr_benc top, *args, *fields; tr_bencInitDict( &top, 3 ); args = tr_bencDictAddDict( &top, "arguments", 0 ); switch( c ) { case TR_OPT_UNK: if( addingTorrents ) { char * tmp = getEncodedMetainfo( optarg ); tr_bencDictAddStr( &top, "method", "torrent-add" ); tr_bencDictAddStr( args, "metainfo", tmp ); tr_free( tmp ); } else { fprintf( stderr, "Unknown option: %s\n", optarg ); addArg = FALSE; } break; case 'a': addingTorrents = 1; addArg = FALSE; break; case 'b': debug = 1; addArg = FALSE; break; case 'd': tr_bencDictAddStr( &top, "method", "session-set" ); tr_bencDictAddInt( args, "speed-limit-down", numarg( optarg ) ); tr_bencDictAddInt( args, "speed-limit-down-enabled", 1 ); break; case 'D': tr_bencDictAddStr( &top, "method", "session-set" ); tr_bencDictAddInt( args, "speed-limit-down-enabled", 0 ); break; case 'f': tr_bencDictAddStr( &top, "method", "torrent-get" ); tr_bencDictAddInt( &top, "tag", TAG_FILES ); addIdArg( args, id ); n = TR_N_ELEMENTS( files_keys ); fields = tr_bencDictAddList( args, "fields", n ); for( i=0; i