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