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: remote.c 13475 2012-09-07 04:25:04Z jordan $ 11 */ 12 13#include <assert.h> 14#include <ctype.h> /* isspace */ 15#include <errno.h> 16#include <math.h> 17#include <stdio.h> 18#include <stdlib.h> 19#include <string.h> /* strcmp */ 20 21#ifdef WIN32 22 #include <direct.h> /* getcwd */ 23#else 24 #include <unistd.h> /* getcwd */ 25#endif 26 27#include <event2/buffer.h> 28 29#define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */ 30#include <curl/curl.h> 31 32#include <libtransmission/transmission.h> 33#include <libtransmission/bencode.h> 34#include <libtransmission/rpcimpl.h> 35#include <libtransmission/json.h> 36#include <libtransmission/tr-getopt.h> 37#include <libtransmission/utils.h> 38#include <libtransmission/version.h> 39 40#define MY_NAME "transmission-remote" 41#define DEFAULT_HOST "localhost" 42#define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR) 43#define DEFAULT_URL TR_DEFAULT_RPC_URL_STR "rpc/" 44 45#define ARGUMENTS "arguments" 46 47#define MEM_K 1024 48#define MEM_B_STR "B" 49#define MEM_K_STR "KiB" 50#define MEM_M_STR "MiB" 51#define MEM_G_STR "GiB" 52#define MEM_T_STR "TiB" 53 54#define DISK_K 1000 55#define DISK_B_STR "B" 56#define DISK_K_STR "kB" 57#define DISK_M_STR "MB" 58#define DISK_G_STR "GB" 59#define DISK_T_STR "TB" 60 61#define SPEED_K 1000 62#define SPEED_B_STR "B/s" 63#define SPEED_K_STR "kB/s" 64#define SPEED_M_STR "MB/s" 65#define SPEED_G_STR "GB/s" 66#define SPEED_T_STR "TB/s" 67 68/*** 69**** 70**** Display Utilities 71**** 72***/ 73 74static void 75etaToString( char * buf, size_t buflen, int64_t eta ) 76{ 77 if( eta < 0 ) 78 tr_snprintf( buf, buflen, "Unknown" ); 79 else if( eta < 60 ) 80 tr_snprintf( buf, buflen, "%" PRId64 " sec", eta ); 81 else if( eta < ( 60 * 60 ) ) 82 tr_snprintf( buf, buflen, "%" PRId64 " min", eta / 60 ); 83 else if( eta < ( 60 * 60 * 24 ) ) 84 tr_snprintf( buf, buflen, "%" PRId64 " hrs", eta / ( 60 * 60 ) ); 85 else 86 tr_snprintf( buf, buflen, "%" PRId64 " days", eta / ( 60 * 60 * 24 ) ); 87} 88 89static char* 90tr_strltime( char * buf, int seconds, size_t buflen ) 91{ 92 int days, hours, minutes, total_seconds; 93 char b[128], d[128], h[128], m[128], s[128], t[128]; 94 95 if( seconds < 0 ) 96 seconds = 0; 97 98 total_seconds = seconds; 99 days = seconds / 86400; 100 hours = ( seconds % 86400 ) / 3600; 101 minutes = ( seconds % 3600 ) / 60; 102 seconds = ( seconds % 3600 ) % 60; 103 104 tr_snprintf( d, sizeof( d ), "%d %s", days, days==1?"day":"days" ); 105 tr_snprintf( h, sizeof( h ), "%d %s", hours, hours==1?"hour":"hours" ); 106 tr_snprintf( m, sizeof( m ), "%d %s", minutes, minutes==1?"minute":"minutes" ); 107 tr_snprintf( s, sizeof( s ), "%d %s", seconds, seconds==1?"second":"seconds" ); 108 tr_snprintf( t, sizeof( t ), "%d %s", total_seconds, total_seconds==1?"second":"seconds" ); 109 110 if( days ) 111 { 112 if( days >= 4 || !hours ) 113 tr_strlcpy( b, d, sizeof( b ) ); 114 else 115 tr_snprintf( b, sizeof( b ), "%s, %s", d, h ); 116 } 117 else if( hours ) 118 { 119 if( hours >= 4 || !minutes ) 120 tr_strlcpy( b, h, sizeof( b ) ); 121 else 122 tr_snprintf( b, sizeof( b ), "%s, %s", h, m ); 123 } 124 else if( minutes ) 125 { 126 if( minutes >= 4 || !seconds ) 127 tr_strlcpy( b, m, sizeof( b ) ); 128 else 129 tr_snprintf( b, sizeof( b ), "%s, %s", m, s ); 130 } 131 else tr_strlcpy( b, s, sizeof( b ) ); 132 133 tr_snprintf( buf, buflen, "%s (%s)", b, t ); 134 return buf; 135} 136 137static char* 138strlpercent( char * buf, double x, size_t buflen ) 139{ 140 return tr_strpercent( buf, x, buflen ); 141} 142 143static char* 144strlratio2( char * buf, double ratio, size_t buflen ) 145{ 146 return tr_strratio( buf, buflen, ratio, "Inf" ); 147} 148 149static char* 150strlratio( char * buf, int64_t numerator, int64_t denominator, size_t buflen ) 151{ 152 double ratio; 153 154 if( denominator != 0 ) 155 ratio = numerator / (double)denominator; 156 else if( numerator != 0 ) 157 ratio = TR_RATIO_INF; 158 else 159 ratio = TR_RATIO_NA; 160 161 return strlratio2( buf, ratio, buflen ); 162} 163 164static char* 165strlmem( char * buf, int64_t bytes, size_t buflen ) 166{ 167 if( !bytes ) 168 tr_strlcpy( buf, "None", buflen ); 169 else 170 tr_formatter_mem_B( buf, bytes, buflen ); 171 172 return buf; 173} 174 175static char* 176strlsize( char * buf, int64_t bytes, size_t buflen ) 177{ 178 if( bytes < 0 ) 179 tr_strlcpy( buf, "Unknown", buflen ); 180 else if( bytes == 0 ) 181 tr_strlcpy( buf, "None", buflen ); 182 else 183 tr_formatter_size_B( buf, bytes, buflen ); 184 185 return buf; 186} 187 188enum 189{ 190 TAG_SESSION, 191 TAG_STATS, 192 TAG_DETAILS, 193 TAG_FILES, 194 TAG_LIST, 195 TAG_PEERS, 196 TAG_PIECES, 197 TAG_PORTTEST, 198 TAG_TORRENT_ADD, 199 TAG_TRACKERS 200}; 201 202static const char* 203getUsage( void ) 204{ 205 return 206 MY_NAME" "LONG_VERSION_STRING"\n" 207 "A fast and easy BitTorrent client\n" 208 "http://www.transmissionbt.com/\n" 209 "\n" 210 "Usage: " MY_NAME 211 " [host] [options]\n" 212 " " 213 MY_NAME " [port] [options]\n" 214 " " 215 MY_NAME " [host:port] [options]\n" 216 " " 217 MY_NAME " [http(s?)://host:port/transmission/] [options]\n" 218 "\n" 219 "See the man page for detailed explanations and many examples."; 220} 221 222/*** 223**** 224**** Command-Line Arguments 225**** 226***/ 227 228static tr_option opts[] = 229{ 230 { 'a', "add", "Add torrent files by filename or URL", "a", 0, NULL }, 231 { 970, "alt-speed", "Use the alternate Limits", "as", 0, NULL }, 232 { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", 0, NULL }, 233 { 972, "alt-speed-downlimit", "max alternate download speed (in "SPEED_K_STR")", "asd", 1, "<speed>" }, 234 { 973, "alt-speed-uplimit", "max alternate upload speed (in "SPEED_K_STR")", "asu", 1, "<speed>" }, 235 { 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", 0, NULL }, 236 { 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", 0, NULL }, 237 { 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", NULL, 1, "<time>" }, 238 { 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", NULL, 1, "<time>" }, 239 { 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", NULL, 1, "<days>" }, 240 { 963, "blocklist-update", "Blocklist update", NULL, 0, NULL }, 241 { 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", 1, "<dir>" }, 242 { 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", 0, NULL }, 243 { 'b', "debug", "Print debugging information", "b", 0, NULL }, 244 { 'd', "downlimit", "Set the max download speed in "SPEED_K_STR" for the current torrent(s) or globally", "d", 1, "<speed>" }, 245 { 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", 0, NULL }, 246 { 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", 1, "<size>" }, 247 { 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL }, 248 { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL }, 249 { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL }, 250 { 850, "exit", "Tell the transmission session to shut down", NULL, 0, NULL }, 251 { 940, "files", "List the current torrent(s)' files", "f", 0, NULL }, 252 { 'g', "get", "Mark files for download", "g", 1, "<files>" }, 253 { 'G', "no-get", "Mark files for not downloading", "G", 1, "<files>" }, 254 { 'i', "info", "Show the current torrent(s)' details", "i", 0, NULL }, 255 { 940, "info-files", "List the current torrent(s)' files", "if", 0, NULL }, 256 { 941, "info-peers", "List the current torrent(s)' peers", "ip", 0, NULL }, 257 { 942, "info-pieces", "List the current torrent(s)' pieces", "ic", 0, NULL }, 258 { 943, "info-trackers", "List the current torrent(s)' trackers", "it", 0, NULL }, 259 { 920, "session-info", "Show the session's details", "si", 0, NULL }, 260 { 921, "session-stats", "Show the session's statistics", "st", 0, NULL }, 261 { 'l', "list", "List all torrents", "l", 0, NULL }, 262 { 960, "move", "Move current torrent's data to a new folder", NULL, 1, "<path>" }, 263 { 961, "find", "Tell Transmission where to find a torrent's data", NULL, 1, "<path>" }, 264 { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL }, 265 { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL }, 266 { 'n', "auth", "Set username and password", "n", 1, "<user:pw>" }, 267 { 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", 0, NULL }, 268 { 'N', "netrc", "Set authentication info from a .netrc file", "N", 1, "<file>" }, 269 { 820, "ssl", "Use SSL when talking to daemon", NULL, 0, NULL }, 270 { 'o', "dht", "Enable distributed hash tables (DHT)", "o", 0, NULL }, 271 { 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", 0, NULL }, 272 { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" }, 273 { 962, "port-test", "Port testing", "pt", 0, NULL }, 274 { 'P', "random-port", "Random port for incomping peers", "P", 0, NULL }, 275 { 900, "priority-high", "Try to download these file(s) first", "ph", 1, "<files>" }, 276 { 901, "priority-normal", "Try to download these file(s) normally", "pn", 1, "<files>" }, 277 { 902, "priority-low", "Try to download these file(s) last", "pl", 1, "<files>" }, 278 { 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", 0, NULL }, 279 { 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", 0, NULL }, 280 { 702, "bandwidth-low", "Give this torrent bandwidth left over by high and normal priority torrents", "Bl", 0, NULL }, 281 { 600, "reannounce", "Reannounce the current torrent(s)", NULL, 0, NULL }, 282 { 'r', "remove", "Remove the current torrent(s)", "r", 0, NULL }, 283 { 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", 1, "<max>" }, 284 { 'R', "remove-and-delete", "Remove the current torrent(s) and delete local data", NULL, 0, NULL }, 285 { 800, "torrent-done-script", "Specify a script to run when a torrent finishes", NULL, 1, "<file>" }, 286 { 801, "no-torrent-done-script", "Don't run a script when torrents finish", NULL, 0, NULL }, 287 { 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", 1, "ratio" }, 288 { 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", 0, NULL }, 289 { 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", 0, NULL }, 290 { 953, "global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio", "gsr", 1, "ratio" }, 291 { 954, "no-global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio", "GSR", 0, NULL }, 292 { 710, "tracker-add", "Add a tracker to a torrent", "td", 1, "<tracker>" }, 293 { 712, "tracker-remove", "Remove a tracker from a torrent", "tr", 1, "<trackerId>" }, 294 { 's', "start", "Start the current torrent(s)", "s", 0, NULL }, 295 { 'S', "stop", "Stop the current torrent(s)", "S", 0, NULL }, 296 { 't', "torrent", "Set the current torrent(s)", "t", 1, "<torrent>" }, 297 { 990, "start-paused", "Start added torrents paused", NULL, 0, NULL }, 298 { 991, "no-start-paused", "Start added torrents unpaused", NULL, 0, NULL }, 299 { 992, "trash-torrent", "Delete torrents after adding", NULL, 0, NULL }, 300 { 993, "no-trash-torrent", "Do not delete torrents after adding", NULL, 0, NULL }, 301 { 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", 0, NULL }, 302 { 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", 0, NULL }, 303 { 'u', "uplimit", "Set the max upload speed in "SPEED_K_STR" for the current torrent(s) or globally", "u", 1, "<speed>" }, 304 { 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", 0, NULL }, 305 { 830, "utp", "Enable uTP for peer connections", NULL, 0, NULL }, 306 { 831, "no-utp", "Disable uTP for peer connections", NULL, 0, NULL }, 307 { 'v', "verify", "Verify the current torrent(s)", "v", 0, NULL }, 308 { 'V', "version", "Show version number and exit", "V", 0, NULL }, 309 { 'w', "download-dir", "When adding a new torrent, set its download folder. Otherwise, set the default download folder", "w", 1, "<path>" }, 310 { 'x', "pex", "Enable peer exchange (PEX)", "x", 0, NULL }, 311 { 'X', "no-pex", "Disable peer exchange (PEX)", "X", 0, NULL }, 312 { 'y', "lpd", "Enable local peer discovery (LPD)", "y", 0, NULL }, 313 { 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", 0, NULL }, 314 { 941, "peer-info", "List the current torrent(s)' peers", "pi", 0, NULL }, 315 { 0, NULL, NULL, NULL, 0, NULL } 316}; 317 318static void 319showUsage( void ) 320{ 321 tr_getopt_usage( MY_NAME, getUsage( ), opts ); 322} 323 324static int 325numarg( const char * arg ) 326{ 327 char * end = NULL; 328 const long num = strtol( arg, &end, 10 ); 329 330 if( *end ) 331 { 332 fprintf( stderr, "Not a number: \"%s\"\n", arg ); 333 showUsage( ); 334 exit( EXIT_FAILURE ); 335 } 336 return num; 337} 338 339enum 340{ 341 MODE_TORRENT_START = (1<<0), 342 MODE_TORRENT_STOP = (1<<1), 343 MODE_TORRENT_VERIFY = (1<<2), 344 MODE_TORRENT_REANNOUNCE = (1<<3), 345 MODE_TORRENT_SET = (1<<4), 346 MODE_TORRENT_GET = (1<<5), 347 MODE_TORRENT_ADD = (1<<6), 348 MODE_TORRENT_REMOVE = (1<<7), 349 MODE_TORRENT_SET_LOCATION = (1<<8), 350 MODE_SESSION_SET = (1<<9), 351 MODE_SESSION_GET = (1<<10), 352 MODE_SESSION_STATS = (1<<11), 353 MODE_SESSION_CLOSE = (1<<12), 354 MODE_BLOCKLIST_UPDATE = (1<<13), 355 MODE_PORT_TEST = (1<<14) 356}; 357 358static int 359getOptMode( int val ) 360{ 361 switch( val ) 362 { 363 case TR_OPT_ERR: 364 case TR_OPT_UNK: 365 case 'a': /* add torrent */ 366 case 'b': /* debug */ 367 case 'n': /* auth */ 368 case 810: /* authenv */ 369 case 'N': /* netrc */ 370 case 820: /* UseSSL */ 371 case 't': /* set current torrent */ 372 case 'V': /* show version number */ 373 return 0; 374 375 case 'c': /* incomplete-dir */ 376 case 'C': /* no-incomplete-dir */ 377 case 'e': /* cache */ 378 case 'm': /* portmap */ 379 case 'M': /* "no-portmap */ 380 case 'o': /* dht */ 381 case 'O': /* no-dht */ 382 case 'p': /* incoming peer port */ 383 case 'P': /* random incoming peer port */ 384 case 'x': /* pex */ 385 case 'X': /* no-pex */ 386 case 'y': /* lpd */ 387 case 'Y': /* no-lpd */ 388 case 800: /* torrent-done-script */ 389 case 801: /* no-torrent-done-script */ 390 case 830: /* utp */ 391 case 831: /* no-utp */ 392 case 970: /* alt-speed */ 393 case 971: /* no-alt-speed */ 394 case 972: /* alt-speed-downlimit */ 395 case 973: /* alt-speed-uplimit */ 396 case 974: /* alt-speed-scheduler */ 397 case 975: /* no-alt-speed-scheduler */ 398 case 976: /* alt-speed-time-begin */ 399 case 977: /* alt-speed-time-end */ 400 case 978: /* alt-speed-days */ 401 case 910: /* encryption-required */ 402 case 911: /* encryption-preferred */ 403 case 912: /* encryption-tolerated */ 404 case 953: /* global-seedratio */ 405 case 954: /* no-global-seedratio */ 406 case 990: /* start-paused */ 407 case 991: /* no-start-paused */ 408 case 992: /* trash-torrent */ 409 case 993: /* no-trash-torrent */ 410 return MODE_SESSION_SET; 411 412 case 712: /* tracker-remove */ 413 case 950: /* seedratio */ 414 case 951: /* seedratio-default */ 415 case 952: /* no-seedratio */ 416 case 984: /* honor-session */ 417 case 985: /* no-honor-session */ 418 return MODE_TORRENT_SET; 419 420 case 920: /* session-info */ 421 return MODE_SESSION_GET; 422 423 case 'g': /* get */ 424 case 'G': /* no-get */ 425 case 700: /* torrent priority-high */ 426 case 701: /* torrent priority-normal */ 427 case 702: /* torrent priority-low */ 428 case 710: /* tracker-add */ 429 case 900: /* file priority-high */ 430 case 901: /* file priority-normal */ 431 case 902: /* file priority-low */ 432 return MODE_TORRENT_SET | MODE_TORRENT_ADD; 433 434 case 961: /* find */ 435 return MODE_TORRENT_SET_LOCATION | MODE_TORRENT_ADD; 436 437 case 'i': /* info */ 438 case 'l': /* list all torrents */ 439 case 940: /* info-files */ 440 case 941: /* info-peer */ 441 case 942: /* info-pieces */ 442 case 943: /* info-tracker */ 443 return MODE_TORRENT_GET; 444 445 case 'd': /* download speed limit */ 446 case 'D': /* no download speed limit */ 447 case 'u': /* upload speed limit */ 448 case 'U': /* no upload speed limit */ 449 case 930: /* peers */ 450 return MODE_SESSION_SET | MODE_TORRENT_SET; 451 452 case 's': /* start */ 453 return MODE_TORRENT_START | MODE_TORRENT_ADD; 454 455 case 'S': /* stop */ 456 return MODE_TORRENT_STOP | MODE_TORRENT_ADD; 457 458 case 'w': /* download-dir */ 459 return MODE_SESSION_SET | MODE_TORRENT_ADD; 460 461 case 850: /* session-close */ 462 return MODE_SESSION_CLOSE; 463 464 case 963: /* blocklist-update */ 465 return MODE_BLOCKLIST_UPDATE; 466 467 case 921: /* session-stats */ 468 return MODE_SESSION_STATS; 469 470 case 'v': /* verify */ 471 return MODE_TORRENT_VERIFY; 472 473 case 600: /* reannounce */ 474 return MODE_TORRENT_REANNOUNCE; 475 476 case 962: /* port-test */ 477 return MODE_PORT_TEST; 478 479 case 'r': /* remove */ 480 case 'R': /* remove and delete */ 481 return MODE_TORRENT_REMOVE; 482 483 case 960: /* move */ 484 return MODE_TORRENT_SET_LOCATION; 485 486 default: 487 fprintf( stderr, "unrecognized argument %d\n", val ); 488 assert( "unrecognized argument" && 0 ); 489 return 0; 490 } 491} 492 493static bool debug = 0; 494static char * auth = NULL; 495static char * netrc = NULL; 496static char * sessionId = NULL; 497static bool UseSSL = false; 498 499static char* 500tr_getcwd( void ) 501{ 502 char * result; 503 char buf[2048]; 504#ifdef WIN32 505 result = _getcwd( buf, sizeof( buf ) ); 506#else 507 result = getcwd( buf, sizeof( buf ) ); 508#endif 509 if( result == NULL ) 510 { 511 fprintf( stderr, "getcwd error: \"%s\"", tr_strerror( errno ) ); 512 *buf = '\0'; 513 } 514 return tr_strdup( buf ); 515} 516 517static char* 518absolutify( const char * path ) 519{ 520 char * buf; 521 522 if( *path == '/' ) 523 buf = tr_strdup( path ); 524 else { 525 char * cwd = tr_getcwd( ); 526 buf = tr_buildPath( cwd, path, NULL ); 527 tr_free( cwd ); 528 } 529 530 return buf; 531} 532 533static char* 534getEncodedMetainfo( const char * filename ) 535{ 536 size_t len = 0; 537 char * b64 = NULL; 538 uint8_t * buf = tr_loadFile( filename, &len ); 539 540 if( buf ) 541 { 542 b64 = tr_base64_encode( buf, len, NULL ); 543 tr_free( buf ); 544 } 545 return b64; 546} 547 548static void 549addIdArg( tr_benc * args, const char * id ) 550{ 551 if( !*id ) 552 { 553 fprintf( 554 stderr, 555 "No torrent specified! Please use the -t option first.\n" ); 556 id = "-1"; /* no torrent will have this ID, so should be a no-op */ 557 } 558 if( strcmp( id, "all" ) ) 559 { 560 const char * pch; 561 bool isList = strchr(id,',') || strchr(id,'-'); 562 bool isNum = true; 563 for( pch=id; isNum && *pch; ++pch ) 564 if( !isdigit( *pch ) ) 565 isNum = false; 566 if( isNum || isList ) 567 tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen( id ) ); 568 else 569 tr_bencDictAddStr( args, "ids", id ); /* it's a torrent sha hash */ 570 } 571} 572 573static void 574addTime( tr_benc * args, const char * key, const char * arg ) 575{ 576 int time; 577 bool success = false; 578 579 if( arg && ( strlen( arg ) == 4 ) ) 580 { 581 const char hh[3] = { arg[0], arg[1], '\0' }; 582 const char mm[3] = { arg[2], arg[3], '\0' }; 583 const int hour = atoi( hh ); 584 const int min = atoi( mm ); 585 586 if( 0<=hour && hour<24 && 0<=min && min<60 ) 587 { 588 time = min + ( hour * 60 ); 589 success = true; 590 } 591 } 592 593 if( success ) 594 tr_bencDictAddInt( args, key, time ); 595 else 596 fprintf( stderr, "Please specify the time of day in 'hhmm' format.\n" ); 597} 598 599static void 600addDays( tr_benc * args, const char * key, const char * arg ) 601{ 602 int days = 0; 603 604 if( arg ) 605 { 606 int i; 607 int valueCount; 608 int * values = tr_parseNumberRange( arg, -1, &valueCount ); 609 for( i=0; i<valueCount; ++i ) 610 { 611 if ( values[i] < 0 || values[i] > 7 ) continue; 612 if ( values[i] == 7 ) values[i] = 0; 613 614 days |= 1 << values[i]; 615 } 616 tr_free( values ); 617 } 618 619 if ( days ) 620 tr_bencDictAddInt( args, key, days ); 621 else 622 fprintf( stderr, "Please specify the days of the week in '1-3,4,7' format.\n" ); 623} 624 625static void 626addFiles( tr_benc * args, 627 const char * key, 628 const char * arg ) 629{ 630 tr_benc * files = tr_bencDictAddList( args, key, 100 ); 631 632 if( !*arg ) 633 { 634 fprintf( stderr, "No files specified!\n" ); 635 arg = "-1"; /* no file will have this index, so should be a no-op */ 636 } 637 if( strcmp( arg, "all" ) ) 638 { 639 int i; 640 int valueCount; 641 int * values = tr_parseNumberRange( arg, -1, &valueCount ); 642 for( i=0; i<valueCount; ++i ) 643 tr_bencListAddInt( files, values[i] ); 644 tr_free( values ); 645 } 646} 647 648#define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) ) 649 650static const char * files_keys[] = { 651 "files", 652 "name", 653 "priorities", 654 "wanted" 655}; 656 657static const char * details_keys[] = { 658 "activityDate", 659 "addedDate", 660 "bandwidthPriority", 661 "comment", 662 "corruptEver", 663 "creator", 664 "dateCreated", 665 "desiredAvailable", 666 "doneDate", 667 "downloadDir", 668 "downloadedEver", 669 "downloadLimit", 670 "downloadLimited", 671 "error", 672 "errorString", 673 "eta", 674 "hashString", 675 "haveUnchecked", 676 "haveValid", 677 "honorsSessionLimits", 678 "id", 679 "isFinished", 680 "isPrivate", 681 "leftUntilDone", 682 "magnetLink", 683 "name", 684 "peersConnected", 685 "peersGettingFromUs", 686 "peersSendingToUs", 687 "peer-limit", 688 "pieceCount", 689 "pieceSize", 690 "rateDownload", 691 "rateUpload", 692 "recheckProgress", 693 "secondsDownloading", 694 "secondsSeeding", 695 "seedRatioMode", 696 "seedRatioLimit", 697 "sizeWhenDone", 698 "startDate", 699 "status", 700 "totalSize", 701 "uploadedEver", 702 "uploadLimit", 703 "uploadLimited", 704 "webseeds", 705 "webseedsSendingToUs" 706}; 707 708static const char * list_keys[] = { 709 "error", 710 "errorString", 711 "eta", 712 "id", 713 "isFinished", 714 "leftUntilDone", 715 "name", 716 "peersGettingFromUs", 717 "peersSendingToUs", 718 "rateDownload", 719 "rateUpload", 720 "sizeWhenDone", 721 "status", 722 "uploadRatio" 723}; 724 725static size_t 726writeFunc( void * ptr, size_t size, size_t nmemb, void * buf ) 727{ 728 const size_t byteCount = size * nmemb; 729 evbuffer_add( buf, ptr, byteCount ); 730 return byteCount; 731} 732 733/* look for a session id in the header in case the server gives back a 409 */ 734static size_t 735parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED ) 736{ 737 const char * line = ptr; 738 const size_t line_len = size * nmemb; 739 const char * key = TR_RPC_SESSION_ID_HEADER ": "; 740 const size_t key_len = strlen( key ); 741 742 if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) ) 743 { 744 const char * begin = line + key_len; 745 const char * end = begin; 746 while( !isspace( *end ) ) 747 ++end; 748 tr_free( sessionId ); 749 sessionId = tr_strndup( begin, end-begin ); 750 } 751 752 return line_len; 753} 754 755static long 756getTimeoutSecs( const char * req ) 757{ 758 if( strstr( req, "\"method\":\"blocklist-update\"" ) != NULL ) 759 return 300L; 760 761 return 60L; /* default value */ 762} 763 764static char* 765getStatusString( tr_benc * t, char * buf, size_t buflen ) 766{ 767 int64_t status; 768 bool boolVal; 769 770 if( !tr_bencDictFindInt( t, "status", &status ) ) 771 { 772 *buf = '\0'; 773 } 774 else switch( status ) 775 { 776 case TR_STATUS_DOWNLOAD_WAIT: 777 case TR_STATUS_SEED_WAIT: 778 tr_strlcpy( buf, "Queued", buflen ); 779 break; 780 781 case TR_STATUS_STOPPED: 782 if( tr_bencDictFindBool( t, "isFinished", &boolVal ) && boolVal ) 783 tr_strlcpy( buf, "Finished", buflen ); 784 else 785 tr_strlcpy( buf, "Stopped", buflen ); 786 break; 787 788 case TR_STATUS_CHECK_WAIT: 789 case TR_STATUS_CHECK: { 790 const char * str = status == TR_STATUS_CHECK_WAIT 791 ? "Will Verify" 792 : "Verifying"; 793 double percent; 794 if( tr_bencDictFindReal( t, "recheckProgress", &percent ) ) 795 tr_snprintf( buf, buflen, "%s (%.0f%%)", str, floor(percent*100.0) ); 796 else 797 tr_strlcpy( buf, str, buflen ); 798 799 break; 800 } 801 802 case TR_STATUS_DOWNLOAD: 803 case TR_STATUS_SEED: { 804 int64_t fromUs = 0; 805 int64_t toUs = 0; 806 tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs ); 807 tr_bencDictFindInt( t, "peersSendingToUs", &toUs ); 808 if( fromUs && toUs ) 809 tr_strlcpy( buf, "Up & Down", buflen ); 810 else if( toUs ) 811 tr_strlcpy( buf, "Downloading", buflen ); 812 else if( fromUs ) { 813 int64_t leftUntilDone = 0; 814 tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone ); 815 if( leftUntilDone > 0 ) 816 tr_strlcpy( buf, "Uploading", buflen ); 817 else 818 tr_strlcpy( buf, "Seeding", buflen ); 819 } else { 820 tr_strlcpy( buf, "Idle", buflen ); 821 } 822 break; 823 } 824 825 default: 826 tr_strlcpy( buf, "Unknown", buflen ); 827 break; 828 } 829 830 return buf; 831} 832 833static const char *bandwidthPriorityNames[] = 834 { "Low", "Normal", "High", "Invalid" }; 835 836static void 837printDetails( tr_benc * top ) 838{ 839 tr_benc *args, *torrents; 840 841 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) 842 && ( tr_bencDictFindList( args, "torrents", &torrents ) ) ) 843 { 844 int ti, tCount; 845 for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount; 846 ++ti ) 847 { 848 tr_benc * t = tr_bencListChild( torrents, ti ); 849 tr_benc * l; 850 const char * str; 851 char buf[512]; 852 char buf2[512]; 853 int64_t i, j, k; 854 bool boolVal; 855 double d; 856 857 printf( "NAME\n" ); 858 if( tr_bencDictFindInt( t, "id", &i ) ) 859 printf( " Id: %" PRId64 "\n", i ); 860 if( tr_bencDictFindStr( t, "name", &str ) ) 861 printf( " Name: %s\n", str ); 862 if( tr_bencDictFindStr( t, "hashString", &str ) ) 863 printf( " Hash: %s\n", str ); 864 if( tr_bencDictFindStr( t, "magnetLink", &str ) ) 865 printf( " Magnet: %s\n", str ); 866 printf( "\n" ); 867 868 printf( "TRANSFER\n" ); 869 getStatusString( t, buf, sizeof( buf ) ); 870 printf( " State: %s\n", buf ); 871 872 if( tr_bencDictFindStr( t, "downloadDir", &str ) ) 873 printf( " Location: %s\n", str ); 874 875 if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) 876 && tr_bencDictFindInt( t, "leftUntilDone", &j ) ) 877 { 878 strlpercent( buf, 100.0 * ( i - j ) / i, sizeof( buf ) ); 879 printf( " Percent Done: %s%%\n", buf ); 880 } 881 882 if( tr_bencDictFindInt( t, "eta", &i ) ) 883 printf( " ETA: %s\n", tr_strltime( buf, i, sizeof( buf ) ) ); 884 if( tr_bencDictFindInt( t, "rateDownload", &i ) ) 885 printf( " Download Speed: %s\n", tr_formatter_speed_KBps( buf, i/(double)tr_speed_K, sizeof( buf ) ) ); 886 if( tr_bencDictFindInt( t, "rateUpload", &i ) ) 887 printf( " Upload Speed: %s\n", tr_formatter_speed_KBps( buf, i/(double)tr_speed_K, sizeof( buf ) ) ); 888 if( tr_bencDictFindInt( t, "haveUnchecked", &i ) 889 && tr_bencDictFindInt( t, "haveValid", &j ) ) 890 { 891 strlsize( buf, i + j, sizeof( buf ) ); 892 strlsize( buf2, j, sizeof( buf2 ) ); 893 printf( " Have: %s (%s verified)\n", buf, buf2 ); 894 } 895 896 if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) ) 897 { 898 if( i < 1 ) 899 printf( " Availability: None\n" ); 900 if( tr_bencDictFindInt( t, "desiredAvailable", &j) 901 && tr_bencDictFindInt( t, "leftUntilDone", &k) ) 902 { 903 j += i - k; 904 strlpercent( buf, 100.0 * j / i, sizeof( buf ) ); 905 printf( " Availability: %s%%\n", buf ); 906 } 907 if( tr_bencDictFindInt( t, "totalSize", &j ) ) 908 { 909 strlsize( buf2, i, sizeof( buf2 ) ); 910 strlsize( buf, j, sizeof( buf ) ); 911 printf( " Total size: %s (%s wanted)\n", buf, buf2 ); 912 } 913 } 914 if( tr_bencDictFindInt( t, "downloadedEver", &i ) 915 && tr_bencDictFindInt( t, "uploadedEver", &j ) ) 916 { 917 strlsize( buf, i, sizeof( buf ) ); 918 printf( " Downloaded: %s\n", buf ); 919 strlsize( buf, j, sizeof( buf ) ); 920 printf( " Uploaded: %s\n", buf ); 921 strlratio( buf, j, i, sizeof( buf ) ); 922 printf( " Ratio: %s\n", buf ); 923 } 924 if( tr_bencDictFindInt( t, "corruptEver", &i ) ) 925 { 926 strlsize( buf, i, sizeof( buf ) ); 927 printf( " Corrupt DL: %s\n", buf ); 928 } 929 if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str && 930 tr_bencDictFindInt( t, "error", &i ) && i ) 931 { 932 switch( i ) { 933 case TR_STAT_TRACKER_WARNING: printf( " Tracker gave a warning: %s\n", str ); break; 934 case TR_STAT_TRACKER_ERROR: printf( " Tracker gave an error: %s\n", str ); break; 935 case TR_STAT_LOCAL_ERROR: printf( " Error: %s\n", str ); break; 936 default: break; /* no error */ 937 } 938 } 939 if( tr_bencDictFindInt( t, "peersConnected", &i ) 940 && tr_bencDictFindInt( t, "peersGettingFromUs", &j ) 941 && tr_bencDictFindInt( t, "peersSendingToUs", &k ) ) 942 { 943 printf( 944 " Peers: " 945 "connected to %" PRId64 ", " 946 "uploading to %" PRId64 947 ", " 948 "downloading from %" 949 PRId64 "\n", 950 i, j, k ); 951 } 952 953 if( tr_bencDictFindList( t, "webseeds", &l ) 954 && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) ) 955 { 956 const int64_t n = tr_bencListSize( l ); 957 if( n > 0 ) 958 printf( 959 " Web Seeds: downloading from %" PRId64 " of %" 960 PRId64 961 " web seeds\n", i, n ); 962 } 963 printf( "\n" ); 964 965 printf( "HISTORY\n" ); 966 if( tr_bencDictFindInt( t, "addedDate", &i ) && i ) 967 { 968 const time_t tt = i; 969 printf( " Date added: %s", ctime( &tt ) ); 970 } 971 if( tr_bencDictFindInt( t, "doneDate", &i ) && i ) 972 { 973 const time_t tt = i; 974 printf( " Date finished: %s", ctime( &tt ) ); 975 } 976 if( tr_bencDictFindInt( t, "startDate", &i ) && i ) 977 { 978 const time_t tt = i; 979 printf( " Date started: %s", ctime( &tt ) ); 980 } 981 if( tr_bencDictFindInt( t, "activityDate", &i ) && i ) 982 { 983 const time_t tt = i; 984 printf( " Latest activity: %s", ctime( &tt ) ); 985 } 986 if( tr_bencDictFindInt( t, "secondsDownloading", &i ) && ( i > 0 ) ) 987 printf( " Downloading Time: %s\n", tr_strltime( buf, i, sizeof( buf ) ) ); 988 if( tr_bencDictFindInt( t, "secondsSeeding", &i ) && ( i > 0 ) ) 989 printf( " Seeding Time: %s\n", tr_strltime( buf, i, sizeof( buf ) ) ); 990 printf( "\n" ); 991 992 printf( "ORIGINS\n" ); 993 if( tr_bencDictFindInt( t, "dateCreated", &i ) && i ) 994 { 995 const time_t tt = i; 996 printf( " Date created: %s", ctime( &tt ) ); 997 } 998 if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) ) 999 printf( " Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) ); 1000 if( tr_bencDictFindStr( t, "comment", &str ) && str && *str ) 1001 printf( " Comment: %s\n", str ); 1002 if( tr_bencDictFindStr( t, "creator", &str ) && str && *str ) 1003 printf( " Creator: %s\n", str ); 1004 if( tr_bencDictFindInt( t, "pieceCount", &i ) ) 1005 printf( " Piece Count: %" PRId64 "\n", i ); 1006 if( tr_bencDictFindInt( t, "pieceSize", &i ) ) 1007 printf( " Piece Size: %s\n", strlmem( buf, i, sizeof( buf ) ) ); 1008 printf( "\n" ); 1009 1010 printf( "LIMITS & BANDWIDTH\n" ); 1011 if( tr_bencDictFindBool( t, "downloadLimited", &boolVal ) 1012 && tr_bencDictFindInt( t, "downloadLimit", &i ) ) 1013 { 1014 printf( " Download Limit: " ); 1015 if( boolVal ) 1016 printf( "%s\n", tr_formatter_speed_KBps( buf, i, sizeof( buf ) ) ); 1017 else 1018 printf( "Unlimited\n" ); 1019 } 1020 if( tr_bencDictFindBool( t, "uploadLimited", &boolVal ) 1021 && tr_bencDictFindInt( t, "uploadLimit", &i ) ) 1022 { 1023 printf( " Upload Limit: " ); 1024 if( boolVal ) 1025 printf( "%s\n", tr_formatter_speed_KBps( buf, i, sizeof( buf ) ) ); 1026 else 1027 printf( "Unlimited\n" ); 1028 } 1029 if( tr_bencDictFindInt( t, "seedRatioMode", &i)) 1030 { 1031 switch( i ) { 1032 case TR_RATIOLIMIT_GLOBAL: 1033 printf( " Ratio Limit: Default\n" ); 1034 break; 1035 case TR_RATIOLIMIT_SINGLE: 1036 if( tr_bencDictFindReal( t, "seedRatioLimit", &d)) 1037 printf( " Ratio Limit: %.2f\n", d ); 1038 break; 1039 case TR_RATIOLIMIT_UNLIMITED: 1040 printf( " Ratio Limit: Unlimited\n" ); 1041 break; 1042 default: break; 1043 } 1044 } 1045 if( tr_bencDictFindBool( t, "honorsSessionLimits", &boolVal ) ) 1046 printf( " Honors Session Limits: %s\n", ( boolVal ? "Yes" : "No" ) ); 1047 if( tr_bencDictFindInt ( t, "peer-limit", &i ) ) 1048 printf( " Peer limit: %" PRId64 "\n", i ); 1049 if (tr_bencDictFindInt (t, "bandwidthPriority", &i)) 1050 printf (" Bandwidth Priority: %s\n", 1051 bandwidthPriorityNames[(i + 1) & 3]); 1052 1053 printf( "\n" ); 1054 } 1055 } 1056} 1057 1058static void 1059printFileList( tr_benc * top ) 1060{ 1061 tr_benc *args, *torrents; 1062 1063 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) 1064 && ( tr_bencDictFindList( args, "torrents", &torrents ) ) ) 1065 { 1066 int i, in; 1067 for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i ) 1068 { 1069 tr_benc * d = tr_bencListChild( torrents, i ); 1070 tr_benc * files, *priorities, *wanteds; 1071 const char * name; 1072 if( tr_bencDictFindStr( d, "name", &name ) 1073 && tr_bencDictFindList( d, "files", &files ) 1074 && tr_bencDictFindList( d, "priorities", &priorities ) 1075 && tr_bencDictFindList( d, "wanted", &wanteds ) ) 1076 { 1077 int j = 0, jn = tr_bencListSize( files ); 1078 printf( "%s (%d files):\n", name, jn ); 1079 printf( "%3s %4s %8s %3s %9s %s\n", "#", "Done", 1080 "Priority", "Get", "Size", 1081 "Name" ); 1082 for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j ) 1083 { 1084 int64_t have; 1085 int64_t length; 1086 int64_t priority; 1087 int64_t wanted; 1088 const char * filename; 1089 tr_benc * file = tr_bencListChild( files, j ); 1090 if( tr_bencDictFindInt( file, "length", &length ) 1091 && tr_bencDictFindStr( file, "name", &filename ) 1092 && tr_bencDictFindInt( file, "bytesCompleted", &have ) 1093 && tr_bencGetInt( tr_bencListChild( priorities, 1094 j ), &priority ) 1095 && tr_bencGetInt( tr_bencListChild( wanteds, 1096 j ), &wanted ) ) 1097 { 1098 char sizestr[64]; 1099 double percent = (double)have / length; 1100 const char * pristr; 1101 strlsize( sizestr, length, sizeof( sizestr ) ); 1102 switch( priority ) 1103 { 1104 case TR_PRI_LOW: 1105 pristr = "Low"; break; 1106 1107 case TR_PRI_HIGH: 1108 pristr = "High"; break; 1109 1110 default: 1111 pristr = "Normal"; break; 1112 } 1113 printf( "%3d: %3.0f%% %-8s %-3s %9s %s\n", 1114 j, 1115 floor( 100.0 * percent ), 1116 pristr, 1117 ( wanted ? "Yes" : "No" ), 1118 sizestr, 1119 filename ); 1120 } 1121 } 1122 } 1123 } 1124 } 1125} 1126 1127static void 1128printPeersImpl( tr_benc * peers ) 1129{ 1130 int i, n; 1131 printf( "%-20s %-12s %-5s %-6s %-6s %s\n", 1132 "Address", "Flags", "Done", "Down", "Up", "Client" ); 1133 for( i = 0, n = tr_bencListSize( peers ); i < n; ++i ) 1134 { 1135 double progress; 1136 const char * address, * client, * flagstr; 1137 int64_t rateToClient, rateToPeer; 1138 tr_benc * d = tr_bencListChild( peers, i ); 1139 1140 if( tr_bencDictFindStr( d, "address", &address ) 1141 && tr_bencDictFindStr( d, "clientName", &client ) 1142 && tr_bencDictFindReal( d, "progress", &progress ) 1143 && tr_bencDictFindStr( d, "flagStr", &flagstr ) 1144 && tr_bencDictFindInt( d, "rateToClient", &rateToClient ) 1145 && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) ) 1146 { 1147 printf( "%-20s %-12s %-5.1f %6.1f %6.1f %s\n", 1148 address, flagstr, (progress*100.0), 1149 rateToClient / (double)tr_speed_K, 1150 rateToPeer / (double)tr_speed_K, 1151 client ); 1152 } 1153 } 1154} 1155 1156static void 1157printPeers( tr_benc * top ) 1158{ 1159 tr_benc *args, *torrents; 1160 1161 if( tr_bencDictFindDict( top, "arguments", &args ) 1162 && tr_bencDictFindList( args, "torrents", &torrents ) ) 1163 { 1164 int i, n; 1165 for( i=0, n=tr_bencListSize( torrents ); i<n; ++i ) 1166 { 1167 tr_benc * peers; 1168 tr_benc * torrent = tr_bencListChild( torrents, i ); 1169 if( tr_bencDictFindList( torrent, "peers", &peers ) ) { 1170 printPeersImpl( peers ); 1171 if( i+1<n ) 1172 printf( "\n" ); 1173 } 1174 } 1175 } 1176} 1177 1178static void 1179printPiecesImpl( const uint8_t * raw, size_t rawlen, int64_t j ) 1180{ 1181 int i, k, len; 1182 char * str = tr_base64_decode( raw, rawlen, &len ); 1183 printf( " " ); 1184 for( i=k=0; k<len; ++k ) { 1185 int e; 1186 for( e=0; i<j && e<8; ++e, ++i ) 1187 printf( "%c", str[k] & (1<<(7-e)) ? '1' : '0' ); 1188 printf( " " ); 1189 if( !(i%64) ) 1190 printf( "\n " ); 1191 } 1192 printf( "\n" ); 1193 tr_free( str ); 1194} 1195 1196static void 1197printPieces( tr_benc * top ) 1198{ 1199 tr_benc *args, *torrents; 1200 1201 if( tr_bencDictFindDict( top, "arguments", &args ) 1202 && tr_bencDictFindList( args, "torrents", &torrents ) ) 1203 { 1204 int i, n; 1205 for( i=0, n=tr_bencListSize( torrents ); i<n; ++i ) 1206 { 1207 int64_t j; 1208 const uint8_t * raw; 1209 size_t rawlen; 1210 tr_benc * torrent = tr_bencListChild( torrents, i ); 1211 if( tr_bencDictFindRaw( torrent, "pieces", &raw, &rawlen ) && 1212 tr_bencDictFindInt( torrent, "pieceCount", &j ) ) { 1213 printPiecesImpl( raw, rawlen, j ); 1214 if( i+1<n ) 1215 printf( "\n" ); 1216 } 1217 } 1218 } 1219} 1220 1221static void 1222printPortTest( tr_benc * top ) 1223{ 1224 tr_benc *args; 1225 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) ) 1226 { 1227 bool boolVal; 1228 1229 if( tr_bencDictFindBool( args, "port-is-open", &boolVal ) ) 1230 printf( "Port is open: %s\n", ( boolVal ? "Yes" : "No" ) ); 1231 } 1232} 1233 1234static void 1235printTorrentList( tr_benc * top ) 1236{ 1237 tr_benc *args, *list; 1238 1239 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) 1240 && ( tr_bencDictFindList( args, "torrents", &list ) ) ) 1241 { 1242 int i, n; 1243 int64_t total_size=0; 1244 double total_up=0, total_down=0; 1245 char haveStr[32]; 1246 1247 printf( "%-4s %-4s %9s %-8s %6s %6s %-5s %-11s %s\n", 1248 "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status", 1249 "Name" ); 1250 1251 for( i = 0, n = tr_bencListSize( list ); i < n; ++i ) 1252 { 1253 int64_t id, eta, status, up, down; 1254 int64_t sizeWhenDone, leftUntilDone; 1255 double ratio; 1256 const char * name; 1257 tr_benc * d = tr_bencListChild( list, i ); 1258 if( tr_bencDictFindInt( d, "eta", &eta ) 1259 && tr_bencDictFindInt( d, "id", &id ) 1260 && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone ) 1261 && tr_bencDictFindStr( d, "name", &name ) 1262 && tr_bencDictFindInt( d, "rateDownload", &down ) 1263 && tr_bencDictFindInt( d, "rateUpload", &up ) 1264 && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone ) 1265 && tr_bencDictFindInt( d, "status", &status ) 1266 && tr_bencDictFindReal( d, "uploadRatio", &ratio ) ) 1267 { 1268 char etaStr[16]; 1269 char statusStr[64]; 1270 char ratioStr[32]; 1271 char doneStr[8]; 1272 int64_t error; 1273 char errorMark; 1274 1275 if( sizeWhenDone ) 1276 tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) ); 1277 else 1278 tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) ); 1279 1280 strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) ); 1281 1282 if( leftUntilDone || eta != -1 ) 1283 etaToString( etaStr, sizeof( etaStr ), eta ); 1284 else 1285 tr_snprintf( etaStr, sizeof( etaStr ), "Done" ); 1286 if( tr_bencDictFindInt( d, "error", &error ) && error ) 1287 errorMark = '*'; 1288 else 1289 errorMark = ' '; 1290 printf( 1291 "%4d%c %4s %9s %-8s %6.1f %6.1f %5s %-11s %s\n", 1292 (int)id, errorMark, 1293 doneStr, 1294 haveStr, 1295 etaStr, 1296 up/(double)tr_speed_K, 1297 down/(double)tr_speed_K, 1298 strlratio2( ratioStr, ratio, sizeof( ratioStr ) ), 1299 getStatusString( d, statusStr, sizeof( statusStr ) ), 1300 name ); 1301 1302 total_up += up; 1303 total_down += down; 1304 total_size += sizeWhenDone - leftUntilDone; 1305 } 1306 } 1307 1308 printf( "Sum: %9s %6.1f %6.1f\n", 1309 strlsize( haveStr, total_size, sizeof( haveStr ) ), 1310 total_up/(double)tr_speed_K, 1311 total_down/(double)tr_speed_K ); 1312 } 1313} 1314 1315static void 1316printTrackersImpl( tr_benc * trackerStats ) 1317{ 1318 int i; 1319 char buf[512]; 1320 tr_benc * t; 1321 1322 for( i=0; (( t = tr_bencListChild( trackerStats, i ))); ++i ) 1323 { 1324 int64_t downloadCount; 1325 bool hasAnnounced; 1326 bool hasScraped; 1327 const char * host; 1328 int64_t id; 1329 bool isBackup; 1330 int64_t lastAnnouncePeerCount; 1331 const char * lastAnnounceResult; 1332 int64_t lastAnnounceStartTime; 1333 bool lastAnnounceSucceeded; 1334 int64_t lastAnnounceTime; 1335 bool lastAnnounceTimedOut; 1336 const char * lastScrapeResult; 1337 bool lastScrapeSucceeded; 1338 int64_t lastScrapeStartTime; 1339 int64_t lastScrapeTime; 1340 bool lastScrapeTimedOut; 1341 int64_t leecherCount; 1342 int64_t nextAnnounceTime; 1343 int64_t nextScrapeTime; 1344 int64_t seederCount; 1345 int64_t tier; 1346 int64_t announceState; 1347 int64_t scrapeState; 1348 1349 if( tr_bencDictFindInt ( t, "downloadCount", &downloadCount ) && 1350 tr_bencDictFindBool( t, "hasAnnounced", &hasAnnounced ) && 1351 tr_bencDictFindBool( t, "hasScraped", &hasScraped ) && 1352 tr_bencDictFindStr ( t, "host", &host ) && 1353 tr_bencDictFindInt ( t, "id", &id ) && 1354 tr_bencDictFindBool( t, "isBackup", &isBackup ) && 1355 tr_bencDictFindInt ( t, "announceState", &announceState ) && 1356 tr_bencDictFindInt ( t, "scrapeState", &scrapeState ) && 1357 tr_bencDictFindInt ( t, "lastAnnouncePeerCount", &lastAnnouncePeerCount ) && 1358 tr_bencDictFindStr ( t, "lastAnnounceResult", &lastAnnounceResult ) && 1359 tr_bencDictFindInt ( t, "lastAnnounceStartTime", &lastAnnounceStartTime ) && 1360 tr_bencDictFindBool( t, "lastAnnounceSucceeded", &lastAnnounceSucceeded ) && 1361 tr_bencDictFindInt ( t, "lastAnnounceTime", &lastAnnounceTime ) && 1362 tr_bencDictFindBool( t, "lastAnnounceTimedOut", &lastAnnounceTimedOut ) && 1363 tr_bencDictFindStr ( t, "lastScrapeResult", &lastScrapeResult ) && 1364 tr_bencDictFindInt ( t, "lastScrapeStartTime", &lastScrapeStartTime ) && 1365 tr_bencDictFindBool( t, "lastScrapeSucceeded", &lastScrapeSucceeded ) && 1366 tr_bencDictFindInt ( t, "lastScrapeTime", &lastScrapeTime ) && 1367 tr_bencDictFindBool( t, "lastScrapeTimedOut", &lastScrapeTimedOut ) && 1368 tr_bencDictFindInt ( t, "leecherCount", &leecherCount ) && 1369 tr_bencDictFindInt ( t, "nextAnnounceTime", &nextAnnounceTime ) && 1370 tr_bencDictFindInt ( t, "nextScrapeTime", &nextScrapeTime ) && 1371 tr_bencDictFindInt ( t, "seederCount", &seederCount ) && 1372 tr_bencDictFindInt ( t, "tier", &tier ) ) 1373 { 1374 const time_t now = time( NULL ); 1375 1376 printf( "\n" ); 1377 printf( " Tracker %d: %s\n", (int)(id), host ); 1378 if( isBackup ) 1379 printf( " Backup on tier %d\n", (int)tier ); 1380 else 1381 printf( " Active in tier %d\n", (int)tier ); 1382 1383 if( !isBackup ) 1384 { 1385 if( hasAnnounced && announceState != TR_TRACKER_INACTIVE ) 1386 { 1387 tr_strltime( buf, now - lastAnnounceTime, sizeof( buf ) ); 1388 if( lastAnnounceSucceeded ) 1389 printf( " Got a list of %d peers %s ago\n", 1390 (int)lastAnnouncePeerCount, buf ); 1391 else if( lastAnnounceTimedOut ) 1392 printf( " Peer list request timed out; will retry\n" ); 1393 else 1394 printf( " Got an error \"%s\" %s ago\n", 1395 lastAnnounceResult, buf ); 1396 } 1397 1398 switch( announceState ) 1399 { 1400 case TR_TRACKER_INACTIVE: 1401 printf( " No updates scheduled\n" ); 1402 break; 1403 case TR_TRACKER_WAITING: 1404 tr_strltime( buf, nextAnnounceTime - now, sizeof( buf ) ); 1405 printf( " Asking for more peers in %s\n", buf ); 1406 break; 1407 case TR_TRACKER_QUEUED: 1408 printf( " Queued to ask for more peers\n" ); 1409 break; 1410 case TR_TRACKER_ACTIVE: 1411 tr_strltime( buf, now - lastAnnounceStartTime, sizeof( buf ) ); 1412 printf( " Asking for more peers now... %s\n", buf ); 1413 break; 1414 } 1415 1416 if( hasScraped ) 1417 { 1418 tr_strltime( buf, now - lastScrapeTime, sizeof( buf ) ); 1419 if( lastScrapeSucceeded ) 1420 printf( " Tracker had %d seeders and %d leechers %s ago\n", 1421 (int)seederCount, (int)leecherCount, buf ); 1422 else if( lastScrapeTimedOut ) 1423 printf( " Tracker scrape timed out; will retry\n" ); 1424 else 1425 printf( " Got a scrape error \"%s\" %s ago\n", 1426 lastScrapeResult, buf ); 1427 } 1428 1429 switch( scrapeState ) 1430 { 1431 case TR_TRACKER_INACTIVE: 1432 break; 1433 case TR_TRACKER_WAITING: 1434 tr_strltime( buf, nextScrapeTime - now, sizeof( buf ) ); 1435 printf( " Asking for peer counts in %s\n", buf ); 1436 break; 1437 case TR_TRACKER_QUEUED: 1438 printf( " Queued to ask for peer counts\n" ); 1439 break; 1440 case TR_TRACKER_ACTIVE: 1441 tr_strltime( buf, now - lastScrapeStartTime, sizeof( buf ) ); 1442 printf( " Asking for peer counts now... %s\n", buf ); 1443 break; 1444 } 1445 } 1446 } 1447 } 1448} 1449 1450static void 1451printTrackers( tr_benc * top ) 1452{ 1453 tr_benc *args, *torrents; 1454 1455 if( tr_bencDictFindDict( top, "arguments", &args ) 1456 && tr_bencDictFindList( args, "torrents", &torrents ) ) 1457 { 1458 int i, n; 1459 for( i=0, n=tr_bencListSize( torrents ); i<n; ++i ) 1460 { 1461 tr_benc * trackerStats; 1462 tr_benc * torrent = tr_bencListChild( torrents, i ); 1463 if( tr_bencDictFindList( torrent, "trackerStats", &trackerStats ) ) { 1464 printTrackersImpl( trackerStats ); 1465 if( i+1<n ) 1466 printf( "\n" ); 1467 } 1468 } 1469 } 1470} 1471 1472static void 1473printSession( tr_benc * top ) 1474{ 1475 tr_benc *args; 1476 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) ) 1477 { 1478 int64_t i; 1479 char buf[64]; 1480 bool boolVal; 1481 const char * str; 1482 1483 printf( "VERSION\n" ); 1484 if( tr_bencDictFindStr( args, "version", &str ) ) 1485 printf( " Daemon version: %s\n", str ); 1486 if( tr_bencDictFindInt( args, "rpc-version", &i ) ) 1487 printf( " RPC version: %" PRId64 "\n", i ); 1488 if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) ) 1489 printf( " RPC minimum version: %" PRId64 "\n", i ); 1490 printf( "\n" ); 1491 1492 printf( "CONFIG\n" ); 1493 if( tr_bencDictFindStr( args, "config-dir", &str ) ) 1494 printf( " Configuration directory: %s\n", str ); 1495 if( tr_bencDictFindStr( args, TR_PREFS_KEY_DOWNLOAD_DIR, &str ) ) 1496 printf( " Download directory: %s\n", str ); 1497 if( tr_bencDictFindInt( args, "download-dir-free-space", &i ) ) 1498 printf( " Download directory free space: %s\n", strlsize( buf, i, sizeof buf ) ); 1499 if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) ) 1500 printf( " Listenport: %" PRId64 "\n", i ); 1501 if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) ) 1502 printf( " Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) ); 1503 if( tr_bencDictFindBool( args, TR_PREFS_KEY_UTP_ENABLED, &boolVal ) ) 1504 printf( " uTP enabled: %s\n", ( boolVal ? "Yes" : "No" ) ); 1505 if( tr_bencDictFindBool( args, TR_PREFS_KEY_DHT_ENABLED, &boolVal ) ) 1506 printf( " Distributed hash table enabled: %s\n", ( boolVal ? "Yes" : "No" ) ); 1507 if( tr_bencDictFindBool( args, TR_PREFS_KEY_LPD_ENABLED, &boolVal ) ) 1508 printf( " Local peer discovery enabled: %s\n", ( boolVal ? "Yes" : "No" ) ); 1509 if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) ) 1510 printf( " Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) ); 1511 if( tr_bencDictFindStr( args, TR_PREFS_KEY_ENCRYPTION, &str ) ) 1512 printf( " Encryption: %s\n", str ); 1513 if( tr_bencDictFindInt( args, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, &i ) ) 1514 printf( " Maximum memory cache size: %s\n", tr_formatter_mem_MB( buf, i, sizeof( buf ) ) ); 1515 printf( "\n" ); 1516 1517 { 1518 bool altEnabled, altTimeEnabled, upEnabled, downEnabled, seedRatioLimited; 1519 int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit; 1520 double seedRatioLimit; 1521 1522 if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, &altDown ) && 1523 tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) && 1524 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) && 1525 tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) && 1526 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) && 1527 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) && 1528 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP_KBps, &altUp ) && 1529 tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) && 1530 tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED_KBps, &downLimit ) && 1531 tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) && 1532 tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED_KBps, &upLimit ) && 1533 tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) && 1534 tr_bencDictFindReal( args, "seedRatioLimit", &seedRatioLimit ) && 1535 tr_bencDictFindBool( args, "seedRatioLimited", &seedRatioLimited) ) 1536 { 1537 char buf[128]; 1538 char buf2[128]; 1539 char buf3[128]; 1540 1541 printf( "LIMITS\n" ); 1542 printf( " Peer limit: %" PRId64 "\n", peerLimit ); 1543 1544 if( seedRatioLimited ) 1545 tr_snprintf( buf, sizeof( buf ), "%.2f", seedRatioLimit ); 1546 else 1547 tr_strlcpy( buf, "Unlimited", sizeof( buf ) ); 1548 printf( " Default seed ratio limit: %s\n", buf ); 1549 1550 if( altEnabled ) 1551 tr_formatter_speed_KBps( buf, altUp, sizeof( buf ) ); 1552 else if( upEnabled ) 1553 tr_formatter_speed_KBps( buf, upLimit, sizeof( buf ) ); 1554 else 1555 tr_strlcpy( buf, "Unlimited", sizeof( buf ) ); 1556 printf( " Upload speed limit: %s (%s limit: %s; %s turtle limit: %s)\n", 1557 buf, 1558 upEnabled ? "Enabled" : "Disabled", 1559 tr_formatter_speed_KBps( buf2, upLimit, sizeof( buf2 ) ), 1560 altEnabled ? "Enabled" : "Disabled", 1561 tr_formatter_speed_KBps( buf3, altUp, sizeof( buf3 ) ) ); 1562 1563 if( altEnabled ) 1564 tr_formatter_speed_KBps( buf, altDown, sizeof( buf ) ); 1565 else if( downEnabled ) 1566 tr_formatter_speed_KBps( buf, downLimit, sizeof( buf ) ); 1567 else 1568 tr_strlcpy( buf, "Unlimited", sizeof( buf ) ); 1569 printf( " Download speed limit: %s (%s limit: %s; %s turtle limit: %s)\n", 1570 buf, 1571 downEnabled ? "Enabled" : "Disabled", 1572 tr_formatter_speed_KBps( buf2, downLimit, sizeof( buf2 ) ), 1573 altEnabled ? "Enabled" : "Disabled", 1574 tr_formatter_speed_KBps( buf3, altDown, sizeof( buf3 ) ) ); 1575 1576 if( altTimeEnabled ) { 1577 printf( " Turtle schedule: %02d:%02d - %02d:%02d ", 1578 (int)(altBegin/60), (int)(altBegin%60), 1579 (int)(altEnd/60), (int)(altEnd%60) ); 1580 if( altDay & TR_SCHED_SUN ) printf( "Sun " ); 1581 if( altDay & TR_SCHED_MON ) printf( "Mon " ); 1582 if( altDay & TR_SCHED_TUES ) printf( "Tue " ); 1583 if( altDay & TR_SCHED_WED ) printf( "Wed " ); 1584 if( altDay & TR_SCHED_THURS ) printf( "Thu " ); 1585 if( altDay & TR_SCHED_FRI ) printf( "Fri " ); 1586 if( altDay & TR_SCHED_SAT ) printf( "Sat " ); 1587 printf( "\n" ); 1588 } 1589 } 1590 } 1591 printf( "\n" ); 1592 1593 printf( "MISC\n" ); 1594 if( tr_bencDictFindBool( args, TR_PREFS_KEY_START, &boolVal ) ) 1595 printf( " Autostart added torrents: %s\n", ( boolVal ? "Yes" : "No" ) ); 1596 if( tr_bencDictFindBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, &boolVal ) ) 1597 printf( " Delete automatically added torrents: %s\n", ( boolVal ? "Yes" : "No" ) ); 1598 } 1599} 1600 1601static void 1602printSessionStats( tr_benc * top ) 1603{ 1604 tr_benc *args, *d; 1605 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) ) 1606 { 1607 char buf[512]; 1608 int64_t up, down, secs, sessions; 1609 1610 if( tr_bencDictFindDict( args, "current-stats", &d ) 1611 && tr_bencDictFindInt( d, "uploadedBytes", &up ) 1612 && tr_bencDictFindInt( d, "downloadedBytes", &down ) 1613 && tr_bencDictFindInt( d, "secondsActive", &secs ) ) 1614 { 1615 printf( "\nCURRENT SESSION\n" ); 1616 printf( " Uploaded: %s\n", strlsize( buf, up, sizeof( buf ) ) ); 1617 printf( " Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) ); 1618 printf( " Ratio: %s\n", strlratio( buf, up, down, sizeof( buf ) ) ); 1619 printf( " Duration: %s\n", tr_strltime( buf, secs, sizeof( buf ) ) ); 1620 } 1621 1622 if( tr_bencDictFindDict( args, "cumulative-stats", &d ) 1623 && tr_bencDictFindInt( d, "sessionCount", &sessions ) 1624 && tr_bencDictFindInt( d, "uploadedBytes", &up ) 1625 && tr_bencDictFindInt( d, "downloadedBytes", &down ) 1626 && tr_bencDictFindInt( d, "secondsActive", &secs ) ) 1627 { 1628 printf( "\nTOTAL\n" ); 1629 printf( " Started %lu times\n", (unsigned long)sessions ); 1630 printf( " Uploaded: %s\n", strlsize( buf, up, sizeof( buf ) ) ); 1631 printf( " Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) ); 1632 printf( " Ratio: %s\n", strlratio( buf, up, down, sizeof( buf ) ) ); 1633 printf( " Duration: %s\n", tr_strltime( buf, secs, sizeof( buf ) ) ); 1634 } 1635 } 1636} 1637 1638static char id[4096]; 1639 1640static int 1641processResponse( const char * rpcurl, const void * response, size_t len ) 1642{ 1643 tr_benc top; 1644 int status = EXIT_SUCCESS; 1645 1646 if( debug ) 1647 fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n", 1648 (int)len, (int)len, (int)len, (const char*) response ); 1649 1650 if( tr_jsonParse( NULL, response, len, &top, NULL ) ) 1651 { 1652 tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, 1653 (int)len, (char*)response ); 1654 status |= EXIT_FAILURE; 1655 } 1656 else 1657 { 1658 int64_t tag = -1; 1659 const char * str; 1660 1661 if(tr_bencDictFindStr(&top, "result", &str)) 1662 { 1663 if( strcmp( str, "success") ) 1664 { 1665 printf( "Error: %s\n", str ); 1666 status |= EXIT_FAILURE; 1667 } 1668 else 1669 { 1670 tr_bencDictFindInt( &top, "tag", &tag ); 1671 1672 switch( tag ) 1673 { 1674 case TAG_SESSION: 1675 printSession( &top ); break; 1676 1677 case TAG_STATS: 1678 printSessionStats( &top ); break; 1679 1680 case TAG_DETAILS: 1681 printDetails( &top ); break; 1682 1683 case TAG_FILES: 1684 printFileList( &top ); break; 1685 1686 case TAG_LIST: 1687 printTorrentList( &top ); break; 1688 1689 case TAG_PEERS: 1690 printPeers( &top ); break; 1691 1692 case TAG_PIECES: 1693 printPieces( &top ); break; 1694 1695 case TAG_PORTTEST: 1696 printPortTest( &top ); break; 1697 1698 case TAG_TRACKERS: 1699 printTrackers( &top ); break; 1700 1701 case TAG_TORRENT_ADD: { 1702 int64_t i; 1703 tr_benc * b = ⊤ 1704 if( tr_bencDictFindDict( &top, ARGUMENTS, &b ) 1705 && tr_bencDictFindDict( b, "torrent-added", &b ) 1706 && tr_bencDictFindInt( b, "id", &i ) ) 1707 tr_snprintf( id, sizeof(id), "%"PRId64, i ); 1708 /* fall-through to default: to give success or failure msg */ 1709 } 1710 default: 1711 if( !tr_bencDictFindStr( &top, "result", &str ) ) 1712 status |= EXIT_FAILURE; 1713 else { 1714 printf( "%s responded: \"%s\"\n", rpcurl, str ); 1715 if( strcmp( str, "success") ) 1716 status |= EXIT_FAILURE; 1717 } 1718 } 1719 1720 tr_bencFree( &top ); 1721 } 1722 } 1723 else 1724 status |= EXIT_FAILURE; 1725 } 1726 1727 return status; 1728} 1729 1730static CURL* 1731tr_curl_easy_init( struct evbuffer * writebuf ) 1732{ 1733 CURL * curl = curl_easy_init( ); 1734 curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING ); 1735 curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc ); 1736 curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf ); 1737 curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader ); 1738 curl_easy_setopt( curl, CURLOPT_POST, 1 ); 1739 curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL ); 1740 curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY ); 1741 curl_easy_setopt( curl, CURLOPT_VERBOSE, debug ); 1742 curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */ 1743 if( netrc ) 1744 curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc ); 1745 if( auth ) 1746 curl_easy_setopt( curl, CURLOPT_USERPWD, auth ); 1747 if( UseSSL ) 1748 curl_easy_setopt( curl, CURLOPT_SSL_VERIFYPEER, 0 ); /* since most certs will be self-signed, do not verify against CA */ 1749 if( sessionId ) { 1750 char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId ); 1751 struct curl_slist * custom_headers = curl_slist_append( NULL, h ); 1752 curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers ); 1753 /* fixme: leaks */ 1754 } 1755 return curl; 1756} 1757 1758static int 1759flush( const char * rpcurl, tr_benc ** benc ) 1760{ 1761 CURLcode res; 1762 CURL * curl; 1763 int status = EXIT_SUCCESS; 1764 struct evbuffer * buf = evbuffer_new( ); 1765 char * json = tr_bencToStr( *benc, TR_FMT_JSON_LEAN, NULL ); 1766 char *rpcurl_http = tr_strdup_printf( UseSSL? "https://%s" : "http://%s", rpcurl ); 1767 1768 curl = tr_curl_easy_init( buf ); 1769 curl_easy_setopt( curl, CURLOPT_URL, rpcurl_http ); 1770 curl_easy_setopt( curl, CURLOPT_POSTFIELDS, json ); 1771 curl_easy_setopt( curl, CURLOPT_TIMEOUT, getTimeoutSecs( json ) ); 1772 1773 if( debug ) 1774 fprintf( stderr, "posting:\n--------\n%s\n--------\n", json ); 1775 1776 if(( res = curl_easy_perform( curl ))) 1777 { 1778 tr_nerr( MY_NAME, "(%s) %s", rpcurl_http, curl_easy_strerror( res ) ); 1779 status |= EXIT_FAILURE; 1780 } 1781 else 1782 { 1783 long response; 1784 curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response ); 1785 switch( response ) { 1786 case 200: 1787 status |= processResponse( rpcurl, (const char*) evbuffer_pullup( buf, -1 ), evbuffer_get_length( buf ) ); 1788 break; 1789 case 409: 1790 /* Session id failed. Our curl header func has already 1791 * pulled the new session id from this response's headers, 1792 * build a new CURL* and try again */ 1793 curl_easy_cleanup( curl ); 1794 curl = NULL; 1795 status |= flush( rpcurl, benc ); 1796 benc = NULL; 1797 break; 1798 default: 1799 fprintf( stderr, "Unexpected response: %s\n", evbuffer_pullup( buf, -1 ) ); 1800 status |= EXIT_FAILURE; 1801 break; 1802 } 1803 } 1804 1805 /* cleanup */ 1806 tr_free( rpcurl_http ); 1807 tr_free( json ); 1808 evbuffer_free( buf ); 1809 if( curl != 0 ) 1810 curl_easy_cleanup( curl ); 1811 if( benc != NULL ) { 1812 tr_bencFree( *benc ); 1813 *benc = 0; 1814 } 1815 return status; 1816} 1817 1818static tr_benc* 1819ensure_sset( tr_benc ** sset ) 1820{ 1821 tr_benc * args; 1822 1823 if( *sset ) 1824 args = tr_bencDictFind( *sset, ARGUMENTS ); 1825 else { 1826 *sset = tr_new0( tr_benc, 1 ); 1827 tr_bencInitDict( *sset, 3 ); 1828 tr_bencDictAddStr( *sset, "method", "session-set" ); 1829 args = tr_bencDictAddDict( *sset, ARGUMENTS, 0 ); 1830 } 1831 1832 return args; 1833} 1834 1835static tr_benc* 1836ensure_tset( tr_benc ** tset ) 1837{ 1838 tr_benc * args; 1839 1840 if( *tset ) 1841 args = tr_bencDictFind( *tset, ARGUMENTS ); 1842 else { 1843 *tset = tr_new0( tr_benc, 1 ); 1844 tr_bencInitDict( *tset, 3 ); 1845 tr_bencDictAddStr( *tset, "method", "torrent-set" ); 1846 args = tr_bencDictAddDict( *tset, ARGUMENTS, 1 ); 1847 } 1848 1849 return args; 1850} 1851 1852static int 1853processArgs( const char * rpcurl, int argc, const char ** argv ) 1854{ 1855 int c; 1856 int status = EXIT_SUCCESS; 1857 const char * optarg; 1858 tr_benc *sset = 0; 1859 tr_benc *tset = 0; 1860 tr_benc *tadd = 0; 1861 1862 *id = '\0'; 1863 1864 while(( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg ))) 1865 { 1866 const int stepMode = getOptMode( c ); 1867 1868 if( !stepMode ) /* meta commands */ 1869 { 1870 switch( c ) 1871 { 1872 case 'a': /* add torrent */ 1873 if( sset != 0 ) status |= flush( rpcurl, &sset ); 1874 if( tadd != 0 ) status |= flush( rpcurl, &tadd ); 1875 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); } 1876 tadd = tr_new0( tr_benc, 1 ); 1877 tr_bencInitDict( tadd, 3 ); 1878 tr_bencDictAddStr( tadd, "method", "torrent-add" ); 1879 tr_bencDictAddInt( tadd, "tag", TAG_TORRENT_ADD ); 1880 tr_bencDictAddDict( tadd, ARGUMENTS, 0 ); 1881 break; 1882 1883 case 'b': /* debug */ 1884 debug = true; 1885 break; 1886 1887 case 'n': /* auth */ 1888 auth = tr_strdup( optarg ); 1889 break; 1890 1891 case 810: /* authenv */ 1892 { 1893 char *authenv = getenv("TR_AUTH"); 1894 if( !authenv ) { 1895 fprintf( stderr, "The TR_AUTH environment variable is not set\n" ); 1896 exit( 0 ); 1897 } 1898 auth = tr_strdup( authenv ); 1899 } 1900 break; 1901 1902 case 'N': /* netrc */ 1903 netrc = tr_strdup( optarg ); 1904 break; 1905 1906 case 820: /* UseSSL */ 1907 UseSSL = true; 1908 break; 1909 1910 case 't': /* set current torrent */ 1911 if( tadd != 0 ) status |= flush( rpcurl, &tadd ); 1912 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); } 1913 tr_strlcpy( id, optarg, sizeof( id ) ); 1914 break; 1915 1916 case 'V': /* show version number */ 1917 fprintf( stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING ); 1918 exit( 0 ); 1919 break; 1920 1921 case TR_OPT_ERR: 1922 fprintf( stderr, "invalid option\n" ); 1923 showUsage( ); 1924 status |= EXIT_FAILURE; 1925 break; 1926 1927 case TR_OPT_UNK: 1928 if( tadd ) { 1929 tr_benc * args = tr_bencDictFind( tadd, ARGUMENTS ); 1930 char * tmp = getEncodedMetainfo( optarg ); 1931 if( tmp ) 1932 tr_bencDictAddStr( args, "metainfo", tmp ); 1933 else 1934 tr_bencDictAddStr( args, "filename", optarg ); 1935 tr_free( tmp ); 1936 } else { 1937 fprintf( stderr, "Unknown option: %s\n", optarg ); 1938 status |= EXIT_FAILURE; 1939 } 1940 break; 1941 } 1942 } 1943 else if( stepMode == MODE_TORRENT_GET ) 1944 { 1945 size_t i, n; 1946 tr_benc * top = tr_new0( tr_benc, 1 ); 1947 tr_benc * args; 1948 tr_benc * fields; 1949 tr_bencInitDict( top, 3 ); 1950 tr_bencDictAddStr( top, "method", "torrent-get" ); 1951 args = tr_bencDictAddDict( top, ARGUMENTS, 0 ); 1952 fields = tr_bencDictAddList( args, "fields", 0 ); 1953 1954 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); } 1955 1956 switch( c ) 1957 { 1958 case 'i': tr_bencDictAddInt( top, "tag", TAG_DETAILS ); 1959 n = TR_N_ELEMENTS( details_keys ); 1960 for( i=0; i<n; ++i ) tr_bencListAddStr( fields, details_keys[i] ); 1961 addIdArg( args, id ); 1962 break; 1963 case 'l': tr_bencDictAddInt( top, "tag", TAG_LIST ); 1964 n = TR_N_ELEMENTS( list_keys ); 1965 for( i=0; i<n; ++i ) tr_bencListAddStr( fields, list_keys[i] ); 1966 break; 1967 case 940: tr_bencDictAddInt( top, "tag", TAG_FILES ); 1968 n = TR_N_ELEMENTS( files_keys ); 1969 for( i=0; i<n; ++i ) tr_bencListAddStr( fields, files_keys[i] ); 1970 addIdArg( args, id ); 1971 break; 1972 case 941: tr_bencDictAddInt( top, "tag", TAG_PEERS ); 1973 tr_bencListAddStr( fields, "peers" ); 1974 addIdArg( args, id ); 1975 break; 1976 case 942: tr_bencDictAddInt( top, "tag", TAG_PIECES ); 1977 tr_bencListAddStr( fields, "pieces" ); 1978 tr_bencListAddStr( fields, "pieceCount" ); 1979 addIdArg( args, id ); 1980 break; 1981 case 943: tr_bencDictAddInt( top, "tag", TAG_TRACKERS ); 1982 tr_bencListAddStr( fields, "trackerStats" ); 1983 addIdArg( args, id ); 1984 break; 1985 default: assert( "unhandled value" && 0 ); 1986 } 1987 1988 status |= flush( rpcurl, &top ); 1989 } 1990 else if( stepMode == MODE_SESSION_SET ) 1991 { 1992 tr_benc * args = ensure_sset( &sset ); 1993 1994 switch( c ) 1995 { 1996 case 800: tr_bencDictAddStr( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, optarg ); 1997 tr_bencDictAddBool( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, true ); 1998 break; 1999 case 801: tr_bencDictAddBool( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, false ); 2000 break; 2001 case 970: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, true ); 2002 break; 2003 case 971: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, false ); 2004 break; 2005 case 972: tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, numarg( optarg ) ); 2006 break; 2007 case 973: tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_UP_KBps, numarg( optarg ) ); 2008 break; 2009 case 974: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, true ); 2010 break; 2011 case 975: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, false ); 2012 break; 2013 case 976: addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, optarg ); 2014 break; 2015 case 977: addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, optarg ); 2016 break; 2017 case 978: addDays( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, optarg ); 2018 break; 2019 case 'c': tr_bencDictAddStr( args, TR_PREFS_KEY_INCOMPLETE_DIR, optarg ); 2020 tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, true ); 2021 break; 2022 case 'C': tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, false ); 2023 break; 2024 case 'e': tr_bencDictAddInt( args, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, atoi(optarg) ); 2025 break; 2026 case 910: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "required" ); 2027 break; 2028 case 911: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "preferred" ); 2029 break; 2030 case 912: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "tolerated" ); 2031 break; 2032 case 'm': tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, true ); 2033 break; 2034 case 'M': tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, false ); 2035 break; 2036 case 'o': tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, true ); 2037 break; 2038 case 'O': tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, false ); 2039 break; 2040 case 830: tr_bencDictAddBool( args, TR_PREFS_KEY_UTP_ENABLED, true ); 2041 break; 2042 case 831: tr_bencDictAddBool( args, TR_PREFS_KEY_UTP_ENABLED, false ); 2043 break; 2044 case 'p': tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_PORT, numarg( optarg ) ); 2045 break; 2046 case 'P': tr_bencDictAddBool( args, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, true); 2047 break; 2048 case 'x': tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, true ); 2049 break; 2050 case 'X': tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, false ); 2051 break; 2052 case 'y': tr_bencDictAddBool( args, TR_PREFS_KEY_LPD_ENABLED, true ); 2053 break; 2054 case 'Y': tr_bencDictAddBool( args, TR_PREFS_KEY_LPD_ENABLED, false ); 2055 break; 2056 case 953: tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) ); 2057 tr_bencDictAddBool( args, "seedRatioLimited", true ); 2058 break; 2059 case 954: tr_bencDictAddBool( args, "seedRatioLimited", false ); 2060 break; 2061 case 990: tr_bencDictAddBool( args, TR_PREFS_KEY_START, false ); 2062 break; 2063 case 991: tr_bencDictAddBool( args, TR_PREFS_KEY_START, true ); 2064 break; 2065 case 992: tr_bencDictAddBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, true ); 2066 break; 2067 case 993: tr_bencDictAddBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, false ); 2068 break; 2069 default: assert( "unhandled value" && 0 ); 2070 break; 2071 } 2072 } 2073 else if( stepMode == ( MODE_SESSION_SET | MODE_TORRENT_SET ) ) 2074 { 2075 tr_benc * targs = 0; 2076 tr_benc * sargs = 0; 2077 2078 if( *id ) 2079 targs = ensure_tset( &tset ); 2080 else 2081 sargs = ensure_sset( &sset ); 2082 2083 switch( c ) 2084 { 2085 case 'd': if( targs ) { 2086 tr_bencDictAddInt( targs, "downloadLimit", numarg( optarg ) ); 2087 tr_bencDictAddBool( targs, "downloadLimited", true ); 2088 } else { 2089 tr_bencDictAddInt( sargs, TR_PREFS_KEY_DSPEED_KBps, numarg( optarg ) ); 2090 tr_bencDictAddBool( sargs, TR_PREFS_KEY_DSPEED_ENABLED, true ); 2091 } 2092 break; 2093 case 'D': if( targs ) 2094 tr_bencDictAddBool( targs, "downloadLimited", false ); 2095 else 2096 tr_bencDictAddBool( sargs, TR_PREFS_KEY_DSPEED_ENABLED, false ); 2097 break; 2098 case 'u': if( targs ) { 2099 tr_bencDictAddInt( targs, "uploadLimit", numarg( optarg ) ); 2100 tr_bencDictAddBool( targs, "uploadLimited", true ); 2101 } else { 2102 tr_bencDictAddInt( sargs, TR_PREFS_KEY_USPEED_KBps, numarg( optarg ) ); 2103 tr_bencDictAddBool( sargs, TR_PREFS_KEY_USPEED_ENABLED, true ); 2104 } 2105 break; 2106 case 'U': if( targs ) 2107 tr_bencDictAddBool( targs, "uploadLimited", false ); 2108 else 2109 tr_bencDictAddBool( sargs, TR_PREFS_KEY_USPEED_ENABLED, false ); 2110 break; 2111 case 930: if( targs ) 2112 tr_bencDictAddInt( targs, "peer-limit", atoi(optarg) ); 2113 else 2114 tr_bencDictAddInt( sargs, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi(optarg) ); 2115 break; 2116 default: assert( "unhandled value" && 0 ); 2117 break; 2118 } 2119 } 2120 else if( stepMode == MODE_TORRENT_SET ) 2121 { 2122 tr_benc * args = ensure_tset( &tset ); 2123 2124 switch( c ) 2125 { 2126 case 712: tr_bencListAddInt( tr_bencDictAddList( args, "trackerRemove", 1 ), atoi( optarg ) ); 2127 break; 2128 case 950: tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) ); 2129 tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_SINGLE ); 2130 break; 2131 case 951: tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_GLOBAL ); 2132 break; 2133 case 952: tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_UNLIMITED ); 2134 break; 2135 case 984: tr_bencDictAddBool( args, "honorsSessionLimits", true ); 2136 break; 2137 case 985: tr_bencDictAddBool( args, "honorsSessionLimits", false ); 2138 break; 2139 default: assert( "unhandled value" && 0 ); 2140 break; 2141 } 2142 } 2143 else if( stepMode == ( MODE_TORRENT_SET | MODE_TORRENT_ADD ) ) 2144 { 2145 tr_benc * args; 2146 2147 if( tadd ) 2148 args = tr_bencDictFind( tadd, ARGUMENTS ); 2149 else 2150 args = ensure_tset( &tset ); 2151 2152 switch( c ) 2153 { 2154 case 'g': addFiles( args, "files-wanted", optarg ); 2155 break; 2156 case 'G': addFiles( args, "files-unwanted", optarg ); 2157 break; 2158 case 900: addFiles( args, "priority-high", optarg ); 2159 break; 2160 case 901: addFiles( args, "priority-normal", optarg ); 2161 break; 2162 case 902: addFiles( args, "priority-low", optarg ); 2163 break; 2164 case 700: tr_bencDictAddInt( args, "bandwidthPriority", 1 ); 2165 break; 2166 case 701: tr_bencDictAddInt( args, "bandwidthPriority", 0 ); 2167 break; 2168 case 702: tr_bencDictAddInt( args, "bandwidthPriority", -1 ); 2169 break; 2170 case 710: tr_bencListAddStr( tr_bencDictAddList( args, "trackerAdd", 1 ), optarg ); 2171 break; 2172 default: assert( "unhandled value" && 0 ); 2173 break; 2174 } 2175 } 2176 else if( c == 961 ) /* set location */ 2177 { 2178 if( tadd ) 2179 { 2180 tr_benc * args = tr_bencDictFind( tadd, ARGUMENTS ); 2181 tr_bencDictAddStr( args, "download-dir", optarg ); 2182 } 2183 else 2184 { 2185 tr_benc * args; 2186 tr_benc * top = tr_new0( tr_benc, 1 ); 2187 tr_bencInitDict( top, 2 ); 2188 tr_bencDictAddStr( top, "method", "torrent-set-location" ); 2189 args = tr_bencDictAddDict( top, ARGUMENTS, 3 ); 2190 tr_bencDictAddStr( args, "location", optarg ); 2191 tr_bencDictAddBool( args, "move", false ); 2192 addIdArg( args, id ); 2193 status |= flush( rpcurl, &top ); 2194 break; 2195 } 2196 } 2197 else switch( c ) 2198 { 2199 case 920: /* session-info */ 2200 { 2201 tr_benc * top = tr_new0( tr_benc, 1 ); 2202 tr_bencInitDict( top, 2 ); 2203 tr_bencDictAddStr( top, "method", "session-get" ); 2204 tr_bencDictAddInt( top, "tag", TAG_SESSION ); 2205 status |= flush( rpcurl, &top ); 2206 break; 2207 } 2208 case 's': /* start */ 2209 { 2210 if( tadd ) 2211 tr_bencDictAddBool( tr_bencDictFind( tadd, "arguments" ), "paused", false ); 2212 else { 2213 tr_benc * top = tr_new0( tr_benc, 1 ); 2214 tr_bencInitDict( top, 2 ); 2215 tr_bencDictAddStr( top, "method", "torrent-start" ); 2216 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id ); 2217 status |= flush( rpcurl, &top ); 2218 } 2219 break; 2220 } 2221 case 'S': /* stop */ 2222 { 2223 if( tadd ) 2224 tr_bencDictAddBool( tr_bencDictFind( tadd, "arguments" ), "paused", true ); 2225 else { 2226 tr_benc * top = tr_new0( tr_benc, 1 ); 2227 tr_bencInitDict( top, 2 ); 2228 tr_bencDictAddStr( top, "method", "torrent-stop" ); 2229 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id ); 2230 status |= flush( rpcurl, &top ); 2231 } 2232 break; 2233 } 2234 case 'w': 2235 { 2236 char * path = absolutify( optarg ); 2237 if( tadd ) 2238 tr_bencDictAddStr( tr_bencDictFind( tadd, "arguments" ), "download-dir", path ); 2239 else { 2240 tr_benc * args = ensure_sset( &sset ); 2241 tr_bencDictAddStr( args, "download-dir", path ); 2242 } 2243 tr_free( path ); 2244 break; 2245 } 2246 case 850: 2247 { 2248 tr_benc * top = tr_new0( tr_benc, 1 ); 2249 tr_bencInitDict( top, 1 ); 2250 tr_bencDictAddStr( top, "method", "session-close" ); 2251 status |= flush( rpcurl, &top ); 2252 break; 2253 } 2254 case 963: 2255 { 2256 tr_benc * top = tr_new0( tr_benc, 1 ); 2257 tr_bencInitDict( top, 1 ); 2258 tr_bencDictAddStr( top, "method", "blocklist-update" ); 2259 status |= flush( rpcurl, &top ); 2260 break; 2261 } 2262 case 921: 2263 { 2264 tr_benc * top = tr_new0( tr_benc, 1 ); 2265 tr_bencInitDict( top, 2 ); 2266 tr_bencDictAddStr( top, "method", "session-stats" ); 2267 tr_bencDictAddInt( top, "tag", TAG_STATS ); 2268 status |= flush( rpcurl, &top ); 2269 break; 2270 } 2271 case 962: 2272 { 2273 tr_benc * top = tr_new0( tr_benc, 1 ); 2274 tr_bencInitDict( top, 2 ); 2275 tr_bencDictAddStr( top, "method", "port-test" ); 2276 tr_bencDictAddInt( top, "tag", TAG_PORTTEST ); 2277 status |= flush( rpcurl, &top ); 2278 break; 2279 } 2280 case 600: 2281 { 2282 tr_benc * top; 2283 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); } 2284 top = tr_new0( tr_benc, 1 ); 2285 tr_bencInitDict( top, 2 ); 2286 tr_bencDictAddStr( top, "method", "torrent-reannounce" ); 2287 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id ); 2288 status |= flush( rpcurl, &top ); 2289 break; 2290 } 2291 case 'v': 2292 { 2293 tr_benc * top; 2294 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); } 2295 top = tr_new0( tr_benc, 1 ); 2296 tr_bencInitDict( top, 2 ); 2297 tr_bencDictAddStr( top, "method", "torrent-verify" ); 2298 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id ); 2299 status |= flush( rpcurl, &top ); 2300 break; 2301 } 2302 case 'r': 2303 case 'R': 2304 { 2305 tr_benc * args; 2306 tr_benc * top = tr_new0( tr_benc, 1 ); 2307 tr_bencInitDict( top, 2 ); 2308 tr_bencDictAddStr( top, "method", "torrent-remove" ); 2309 args = tr_bencDictAddDict( top, ARGUMENTS, 2 ); 2310 tr_bencDictAddBool( args, "delete-local-data", c=='R' ); 2311 addIdArg( args, id ); 2312 status |= flush( rpcurl, &top ); 2313 break; 2314 } 2315 case 960: 2316 { 2317 tr_benc * args; 2318 tr_benc * top = tr_new0( tr_benc, 1 ); 2319 tr_bencInitDict( top, 2 ); 2320 tr_bencDictAddStr( top, "method", "torrent-set-location" ); 2321 args = tr_bencDictAddDict( top, ARGUMENTS, 3 ); 2322 tr_bencDictAddStr( args, "location", optarg ); 2323 tr_bencDictAddBool( args, "move", true ); 2324 addIdArg( args, id ); 2325 status |= flush( rpcurl, &top ); 2326 break; 2327 } 2328 default: 2329 { 2330 fprintf( stderr, "got opt [%d]\n", c ); 2331 showUsage( ); 2332 break; 2333 } 2334 } 2335 } 2336 2337 if( tadd != 0 ) status |= flush( rpcurl, &tadd ); 2338 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( rpcurl, &tset ); } 2339 if( sset != 0 ) status |= flush( rpcurl, &sset ); 2340 return status; 2341} 2342 2343/* [host:port] or [host] or [port] or [http(s?)://host:port/transmission/] */ 2344static void 2345getHostAndPortAndRpcUrl( int * argc, char ** argv, 2346 char ** host, int * port, char ** rpcurl ) 2347{ 2348 if( *argv[1] != '-' ) 2349 { 2350 int i; 2351 const char * s = argv[1]; 2352 const char * delim = strchr( s, ':' ); 2353 if( !strncmp(s, "http://", 7 ) ) /* user passed in http rpc url */ 2354 { 2355 *rpcurl = tr_strdup_printf( "%s/rpc/", s + 7 ); 2356 } 2357 else if( !strncmp(s, "https://", 8) ) /* user passed in https rpc url */ 2358 { 2359 UseSSL = true; 2360 *rpcurl = tr_strdup_printf( "%s/rpc/", s + 8 ); 2361 } 2362 else if( delim ) /* user passed in both host and port */ 2363 { 2364 *host = tr_strndup( s, delim - s ); 2365 *port = atoi( delim + 1 ); 2366 } 2367 else 2368 { 2369 char * end; 2370 const int i = strtol( s, &end, 10 ); 2371 if( !*end ) /* user passed in a port */ 2372 *port = i; 2373 else /* user passed in a host */ 2374 *host = tr_strdup( s ); 2375 } 2376 2377 *argc -= 1; 2378 for( i = 1; i < *argc; ++i ) 2379 argv[i] = argv[i + 1]; 2380 } 2381} 2382 2383int 2384main( int argc, char ** argv ) 2385{ 2386 int port = DEFAULT_PORT; 2387 char * host = NULL; 2388 char * rpcurl = NULL; 2389 int exit_status = EXIT_SUCCESS; 2390 2391 if( argc < 2 ) { 2392 showUsage( ); 2393 return EXIT_FAILURE; 2394 } 2395 2396 tr_formatter_mem_init( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR ); 2397 tr_formatter_size_init( DISK_K,DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR ); 2398 tr_formatter_speed_init( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR ); 2399 2400 getHostAndPortAndRpcUrl( &argc, argv, &host, &port, &rpcurl ); 2401 if( host == NULL ) 2402 host = tr_strdup( DEFAULT_HOST ); 2403 if( rpcurl == NULL ) 2404 rpcurl = tr_strdup_printf( "%s:%d%s", host, port, DEFAULT_URL ); 2405 2406 exit_status = processArgs( rpcurl, argc, (const char**)argv ); 2407 2408 tr_free( host ); 2409 tr_free( rpcurl ); 2410 return exit_status; 2411} 2412