1/* 2 * This file Copyright (C) Mnemosyne LLC 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 2 6 * as published by the Free Software Foundation. 7 * 8 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html 9 * 10 * $Id: edit.c 13191 2012-02-03 16:44:07Z jordan $ 11 */ 12 13#include <stdio.h> /* fprintf() */ 14#include <string.h> /* strlen(), strstr(), strcmp() */ 15#include <stdlib.h> /* EXIT_FAILURE */ 16 17#include <event2/buffer.h> 18 19#include <libtransmission/transmission.h> 20#include <libtransmission/bencode.h> 21#include <libtransmission/tr-getopt.h> 22#include <libtransmission/utils.h> 23#include <libtransmission/version.h> 24 25#define MY_NAME "transmission-edit" 26 27static int fileCount = 0; 28static bool showVersion = false; 29static const char ** files = NULL; 30static const char * add = NULL; 31static const char * deleteme = NULL; 32static const char * replace[2] = { NULL, NULL }; 33 34static tr_option options[] = 35{ 36 { 'a', "add", "Add a tracker's announce URL", "a", 1, "<url>" }, 37 { 'd', "delete", "Delete a tracker's announce URL", "d", 1, "<url>" }, 38 { 'r', "replace", "Search and replace a substring in the announce URLs", "r", 1, "<old> <new>" }, 39 { 'V', "version", "Show version number and exit", "V", 0, NULL }, 40 { 0, NULL, NULL, NULL, 0, NULL } 41}; 42 43static const char * 44getUsage( void ) 45{ 46 return "Usage: " MY_NAME " [options] torrent-file(s)"; 47} 48 49static int 50parseCommandLine( int argc, const char ** argv ) 51{ 52 int c; 53 const char * optarg; 54 55 while(( c = tr_getopt( getUsage( ), argc, argv, options, &optarg ))) 56 { 57 switch( c ) 58 { 59 case 'a': add = optarg; 60 break; 61 case 'd': deleteme = optarg; 62 break; 63 case 'r': replace[0] = optarg; 64 c = tr_getopt( getUsage( ), argc, argv, options, &optarg ); 65 if( c != TR_OPT_UNK ) return 1; 66 replace[1] = optarg; 67 break; 68 case 'V': showVersion = true; 69 break; 70 case TR_OPT_UNK: files[fileCount++] = optarg; break; 71 default: return 1; 72 } 73 } 74 75 return 0; 76} 77 78static bool 79removeURL( tr_benc * metainfo, const char * url ) 80{ 81 const char * str; 82 tr_benc * announce_list; 83 bool changed = false; 84 85 if( tr_bencDictFindStr( metainfo, "announce", &str ) && !strcmp( str, url ) ) 86 { 87 printf( "\tRemoved \"%s\" from \"announce\"\n", str ); 88 tr_bencDictRemove( metainfo, "announce" ); 89 changed = true; 90 } 91 92 if( tr_bencDictFindList( metainfo, "announce-list", &announce_list ) ) 93 { 94 tr_benc * tier; 95 int tierIndex = 0; 96 while(( tier = tr_bencListChild( announce_list, tierIndex ))) 97 { 98 tr_benc * node; 99 int nodeIndex = 0; 100 while(( node = tr_bencListChild( tier, nodeIndex ))) 101 { 102 if( tr_bencGetStr( node, &str ) && !strcmp( str, url ) ) 103 { 104 printf( "\tRemoved \"%s\" from \"announce-list\" tier #%d\n", str, (tierIndex+1) ); 105 tr_bencListRemove( tier, nodeIndex ); 106 changed = true; 107 } 108 else ++nodeIndex; 109 } 110 111 if( tr_bencListSize( tier ) == 0 ) 112 { 113 printf( "\tNo URLs left in tier #%d... removing tier\n", (tierIndex+1) ); 114 tr_bencListRemove( announce_list, tierIndex ); 115 } 116 else ++tierIndex; 117 } 118 119 if( tr_bencListSize( announce_list ) == 0 ) 120 { 121 printf( "\tNo tiers left... removing announce-list\n" ); 122 tr_bencDictRemove( metainfo, "announce-list" ); 123 } 124 } 125 126 /* if we removed the "announce" field and there's still another track left, 127 * use it as the "announce" field */ 128 if( changed && !tr_bencDictFindStr( metainfo, "announce", &str ) ) 129 { 130 tr_benc * tier; 131 tr_benc * node; 132 133 if(( tier = tr_bencListChild( announce_list, 0 ))) { 134 if(( node = tr_bencListChild( tier, 0 ))) { 135 if( tr_bencGetStr( node, &str ) ) { 136 tr_bencDictAddStr( metainfo, "announce", str ); 137 printf( "\tAdded \"%s\" to announce\n", str ); 138 } 139 } 140 } 141 } 142 143 return changed; 144} 145 146static char* 147replaceSubstr( const char * str, const char * in, const char * out ) 148{ 149 char * walk; 150 struct evbuffer * buf = evbuffer_new( ); 151 const size_t inlen = strlen( in ); 152 const size_t outlen = strlen( out ); 153 154 while(( walk = strstr( str, in ))) 155 { 156 evbuffer_add( buf, str, walk-str ); 157 evbuffer_add( buf, out, outlen ); 158 str = walk + inlen; 159 } 160 161 evbuffer_add( buf, str, strlen( str ) ); 162 163 return evbuffer_free_to_str( buf ); 164} 165 166static bool 167replaceURL( tr_benc * metainfo, const char * in, const char * out ) 168{ 169 const char * str; 170 tr_benc * announce_list; 171 bool changed = false; 172 173 if( tr_bencDictFindStr( metainfo, "announce", &str ) && strstr( str, in ) ) 174 { 175 char * newstr = replaceSubstr( str, in, out ); 176 printf( "\tReplaced in \"announce\": \"%s\" --> \"%s\"\n", str, newstr ); 177 tr_bencDictAddStr( metainfo, "announce", newstr ); 178 tr_free( newstr ); 179 changed = true; 180 } 181 182 if( tr_bencDictFindList( metainfo, "announce-list", &announce_list ) ) 183 { 184 tr_benc * tier; 185 int tierCount = 0; 186 while(( tier = tr_bencListChild( announce_list, tierCount++ ))) 187 { 188 tr_benc * node; 189 int nodeCount = 0; 190 while(( node = tr_bencListChild( tier, nodeCount++ ))) 191 { 192 if( tr_bencGetStr( node, &str ) && strstr( str, in ) ) 193 { 194 char * newstr = replaceSubstr( str, in, out ); 195 printf( "\tReplaced in \"announce-list\" tier %d: \"%s\" --> \"%s\"\n", tierCount, str, newstr ); 196 tr_bencFree( node ); 197 tr_bencInitStr( node, newstr, -1 ); 198 tr_free( newstr ); 199 changed = true; 200 } 201 } 202 } 203 } 204 205 return changed; 206} 207 208static bool 209announce_list_has_url( tr_benc * announce_list, const char * url ) 210{ 211 tr_benc * tier; 212 int tierCount = 0; 213 while(( tier = tr_bencListChild( announce_list, tierCount++ ))) { 214 tr_benc * node; 215 const char * str; 216 int nodeCount = 0; 217 while(( node = tr_bencListChild( tier, nodeCount++ ))) 218 if( tr_bencGetStr( node, &str ) && !strcmp( str, url ) ) 219 return true; 220 } 221 return false; 222} 223 224static bool 225addURL( tr_benc * metainfo, const char * url ) 226{ 227 const char * announce = NULL; 228 tr_benc * announce_list = NULL; 229 bool changed = false; 230 const bool had_announce = tr_bencDictFindStr( metainfo, "announce", &announce ); 231 const bool had_announce_list = tr_bencDictFindList( metainfo, "announce-list", &announce_list ); 232 233 if( !had_announce && !had_announce_list ) 234 { 235 /* this new tracker is the only one, so add it to "announce"... */ 236 printf( "\tAdded \"%s\" in \"announce\"\n", url ); 237 tr_bencDictAddStr( metainfo, "announce", url ); 238 changed = true; 239 } 240 else 241 { 242 if( !had_announce_list ) 243 { 244 announce_list = tr_bencDictAddList( metainfo, "announce-list", 2 ); 245 246 if( had_announce ) 247 { 248 /* we're moving from an 'announce' to an 'announce-list', 249 * so copy the old announce URL to the list */ 250 tr_benc * tier = tr_bencListAddList( announce_list, 1 ); 251 tr_bencListAddStr( tier, announce ); 252 changed = true; 253 } 254 } 255 256 /* If the user-specified URL isn't in the announce list yet, add it */ 257 if( !announce_list_has_url( announce_list, url ) ) 258 { 259 tr_benc * tier = tr_bencListAddList( announce_list, 1 ); 260 tr_bencListAddStr( tier, url ); 261 printf( "\tAdded \"%s\" to \"announce-list\" tier %zu\n", url, tr_bencListSize( announce_list ) ); 262 changed = true; 263 } 264 } 265 266 return changed; 267} 268 269int 270main( int argc, char * argv[] ) 271{ 272 int i; 273 int changedCount = 0; 274 275 files = tr_new0( const char*, argc ); 276 277 tr_setMessageLevel( TR_MSG_ERR ); 278 279 if( parseCommandLine( argc, (const char**)argv ) ) 280 return EXIT_FAILURE; 281 282 if( showVersion ) 283 { 284 fprintf( stderr, MY_NAME" "LONG_VERSION_STRING"\n" ); 285 return 0; 286 } 287 288 if( fileCount < 1 ) 289 { 290 fprintf( stderr, "ERROR: No torrent files specified.\n" ); 291 tr_getopt_usage( MY_NAME, getUsage( ), options ); 292 fprintf( stderr, "\n" ); 293 return EXIT_FAILURE; 294 } 295 296 if( !add && !deleteme && !replace[0] ) 297 { 298 fprintf( stderr, "ERROR: Must specify -a, -d or -r\n" ); 299 tr_getopt_usage( MY_NAME, getUsage( ), options ); 300 fprintf( stderr, "\n" ); 301 return EXIT_FAILURE; 302 } 303 304 for( i=0; i<fileCount; ++i ) 305 { 306 tr_benc top; 307 bool changed = false; 308 const char * filename = files[i]; 309 310 printf( "%s\n", filename ); 311 312 if( tr_bencLoadFile( &top, TR_FMT_BENC, filename ) ) 313 { 314 printf( "\tError reading file\n" ); 315 continue; 316 } 317 318 if( deleteme != NULL ) 319 changed |= removeURL( &top, deleteme ); 320 321 if( add != NULL ) 322 changed = addURL( &top, add ); 323 324 if( replace[0] && replace[1] ) 325 changed |= replaceURL( &top, replace[0], replace[1] ); 326 327 if( changed ) 328 { 329 ++changedCount; 330 tr_bencToFile( &top, TR_FMT_BENC, filename ); 331 } 332 333 tr_bencFree( &top ); 334 } 335 336 printf( "Changed %d files\n", changedCount ); 337 338 tr_free( files ); 339 return 0; 340} 341