Revert "transmission: update from 2.13 to 2.22"
[tomato.git] / release / src / router / transmission / daemon / remote.c
blob0c7ce232cda8a1dc8fc8499e81f321fee1d1f241
1 /*
2 * This file Copyright (C) 2008-2010 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 11318 2010-10-16 16:08:40Z charles $
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 <event.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)
44 #define ARGUMENTS "arguments"
46 #define MEM_K 1024
47 #define MEM_B_STR "B"
48 #define MEM_K_STR "KiB"
49 #define MEM_M_STR "MiB"
50 #define MEM_G_STR "GiB"
51 #define MEM_T_STR "TiB"
53 #define DISK_K 1024
54 #define DISK_B_STR "B"
55 #define DISK_K_STR "KiB"
56 #define DISK_M_STR "MiB"
57 #define DISK_G_STR "GiB"
58 #define DISK_T_STR "TiB"
60 #define SPEED_K 1024
61 #define SPEED_B_STR "B/s"
62 #define SPEED_K_STR "KiB/s"
63 #define SPEED_M_STR "MiB/s"
64 #define SPEED_G_STR "GiB/s"
65 #define SPEED_T_STR "TiB/s"
67 /***
68 ****
69 **** Display Utilities
70 ****
71 ***/
73 static void
74 etaToString( char * buf, size_t buflen, int64_t eta )
76 if( eta < 0 )
77 tr_snprintf( buf, buflen, "Unknown" );
78 else if( eta < 60 )
79 tr_snprintf( buf, buflen, "%" PRId64 "sec", eta );
80 else if( eta < ( 60 * 60 ) )
81 tr_snprintf( buf, buflen, "%" PRId64 " min", eta / 60 );
82 else if( eta < ( 60 * 60 * 24 ) )
83 tr_snprintf( buf, buflen, "%" PRId64 " hrs", eta / ( 60 * 60 ) );
84 else
85 tr_snprintf( buf, buflen, "%" PRId64 " days", eta / ( 60 * 60 * 24 ) );
88 static char*
89 tr_strltime( char * buf, int seconds, size_t buflen )
91 int days, hours, minutes;
92 char d[128], h[128], m[128], s[128];
94 if( seconds < 0 )
95 seconds = 0;
97 days = seconds / 86400;
98 hours = ( seconds % 86400 ) / 3600;
99 minutes = ( seconds % 3600 ) / 60;
100 seconds = ( seconds % 3600 ) % 60;
102 tr_snprintf( d, sizeof( d ), "%'d day%s", days, days==1?"":"s" );
103 tr_snprintf( h, sizeof( h ), "%'d hour%s", hours, hours==1?"":"s" );
104 tr_snprintf( m, sizeof( m ), "%'d minute%s", minutes, minutes==1?"":"s" );
105 tr_snprintf( s, sizeof( s ), "%'d second%s", seconds, seconds==1?"":"s" );
107 if( days )
109 if( days >= 4 || !hours )
110 tr_strlcpy( buf, d, buflen );
111 else
112 tr_snprintf( buf, buflen, "%s, %s", d, h );
114 else if( hours )
116 if( hours >= 4 || !minutes )
117 tr_strlcpy( buf, h, buflen );
118 else
119 tr_snprintf( buf, buflen, "%s, %s", h, m );
121 else if( minutes )
123 if( minutes >= 4 || !seconds )
124 tr_strlcpy( buf, m, buflen );
125 else
126 tr_snprintf( buf, buflen, "%s, %s", m, s );
128 else tr_strlcpy( buf, s, buflen );
130 return buf;
133 static char*
134 strlpercent( char * buf, double x, size_t buflen )
136 return tr_strpercent( buf, x, buflen );
139 static char*
140 strlratio2( char * buf, double ratio, size_t buflen )
142 return tr_strratio( buf, buflen, ratio, "Inf" );
145 static char*
146 strlratio( char * buf, int64_t numerator, int64_t denominator, size_t buflen )
148 double ratio;
150 if( denominator != 0 )
151 ratio = numerator / (double)denominator;
152 else if( numerator != 0 )
153 ratio = TR_RATIO_INF;
154 else
155 ratio = TR_RATIO_NA;
157 return strlratio2( buf, ratio, buflen );
160 static char*
161 strlmem( char * buf, int64_t bytes, size_t buflen )
163 if( !bytes )
164 tr_strlcpy( buf, "None", buflen );
165 else
166 tr_formatter_mem_B( buf, bytes, buflen );
168 return buf;
171 static char*
172 strlsize( char * buf, int64_t bytes, size_t buflen )
174 if( !bytes )
175 tr_strlcpy( buf, "None", buflen );
176 else
177 tr_formatter_size_B( buf, bytes, buflen );
179 return buf;
182 enum
184 TAG_SESSION,
185 TAG_STATS,
186 TAG_DETAILS,
187 TAG_FILES,
188 TAG_LIST,
189 TAG_PEERS,
190 TAG_PIECES,
191 TAG_PORTTEST,
192 TAG_TORRENT_ADD,
193 TAG_TRACKERS
196 static const char*
197 getUsage( void )
199 return
200 MY_NAME" "LONG_VERSION_STRING"\n"
201 "A fast and easy BitTorrent client\n"
202 "http://www.transmissionbt.com/\n"
203 "\n"
204 "Usage: " MY_NAME
205 " [host] [options]\n"
207 MY_NAME " [port] [options]\n"
209 MY_NAME " [host:port] [options]\n"
210 "\n"
211 "See the man page for detailed explanations and many examples.";
214 /***
215 ****
216 **** Command-Line Arguments
217 ****
218 ***/
220 static tr_option opts[] =
222 { 'a', "add", "Add torrent files by filename or URL", "a", 0, NULL },
223 { 970, "alt-speed", "Use the alternate Limits", "as", 0, NULL },
224 { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", 0, NULL },
225 { 972, "alt-speed-downlimit", "max alternate download speed (in "SPEED_K_STR")", "asd", 1, "<speed>" },
226 { 973, "alt-speed-uplimit", "max alternate upload speed (in "SPEED_K_STR")", "asu", 1, "<speed>" },
227 { 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", 0, NULL },
228 { 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", 0, NULL },
229 { 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", NULL, 1, "<time>" },
230 { 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", NULL, 1, "<time>" },
231 { 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", NULL, 1, "<days>" },
232 { 963, "blocklist-update", "Blocklist update", NULL, 0, NULL },
233 { 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", 1, "<dir>" },
234 { 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", 0, NULL },
235 { 'b', "debug", "Print debugging information", "b", 0, NULL },
236 { 'd', "downlimit", "Set the max download speed in "SPEED_K_STR" for the current torrent(s) or globally", "d", 1, "<speed>" },
237 { 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", 0, NULL },
238 { 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", 1, "<size>" },
239 { 910, "encryption-required", "Encrypt all peer connections", "er", 0, NULL },
240 { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
241 { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
242 { 940, "files", "List the current torrent(s)' files", "f", 0, NULL },
243 { 'g', "get", "Mark files for download", "g", 1, "<files>" },
244 { 'G', "no-get", "Mark files for not downloading", "G", 1, "<files>" },
245 { 'i', "info", "Show the current torrent(s)' details", "i", 0, NULL },
246 { 940, "info-files", "List the current torrent(s)' files", "if", 0, NULL },
247 { 941, "info-peers", "List the current torrent(s)' peers", "ip", 0, NULL },
248 { 942, "info-pieces", "List the current torrent(s)' pieces", "ic", 0, NULL },
249 { 943, "info-trackers", "List the current torrent(s)' trackers", "it", 0, NULL },
250 { 920, "session-info", "Show the session's details", "si", 0, NULL },
251 { 921, "session-stats", "Show the session's statistics", "st", 0, NULL },
252 { 'l', "list", "List all torrents", "l", 0, NULL },
253 { 960, "move", "Move current torrent's data to a new folder", NULL, 1, "<path>" },
254 { 961, "find", "Tell Transmission where to find a torrent's data", NULL, 1, "<path>" },
255 { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", 0, NULL },
256 { 'M', "no-portmap", "Disable portmapping", "M", 0, NULL },
257 { 'n', "auth", "Set username and password", "n", 1, "<user:pw>" },
258 { 'N', "netrc", "Set authentication info from a .netrc file", "N", 1, "<file>" },
259 { 'o', "dht", "Enable distributed hash tables (DHT)", "o", 0, NULL },
260 { 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", 0, NULL },
261 { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
262 { 962, "port-test", "Port testing", "pt", 0, NULL },
263 { 'P', "random-port", "Random port for incomping peers", "P", 0, NULL },
264 { 900, "priority-high", "Try to download these file(s) first", "ph", 1, "<files>" },
265 { 901, "priority-normal", "Try to download these file(s) normally", "pn", 1, "<files>" },
266 { 902, "priority-low", "Try to download these file(s) last", "pl", 1, "<files>" },
267 { 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", 0, NULL },
268 { 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", 0, NULL },
269 { 702, "bandwidth-low", "Give this torrent bandwidth left over by high and normal priority torrents", "Bl", 0, NULL },
270 { 'r', "remove", "Remove the current torrent(s)", "r", 0, NULL },
271 { 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", 1, "<max>" },
272 { 'R', "remove-and-delete", "Remove the current torrent(s) and delete local data", NULL, 0, NULL },
273 { 800, "torrent-done-script", "Specify a script to run when a torrent finishes", NULL, 1, "<file>" },
274 { 801, "no-torrent-done-script", "Don't run a script when torrents finish", NULL, 0, NULL },
275 { 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", 1, "ratio" },
276 { 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", 0, NULL },
277 { 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", 0, NULL },
278 { 953, "global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio", "gsr", 1, "ratio" },
279 { 954, "no-global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio", "GSR", 0, NULL },
280 { 710, "tracker-add", "Add a tracker to a torrent", "ta", 1, "<tracker>" },
281 { 712, "tracker-remove", "Remove a tracker from a torrent", "tr", 1, "<trackerId>" },
282 { 's', "start", "Start the current torrent(s)", "s", 0, NULL },
283 { 'S', "stop", "Stop the current torrent(s)", "S", 0, NULL },
284 { 't', "torrent", "Set the current torrent(s)", "t", 1, "<torrent>" },
285 { 990, "start-paused", "Start added torrents paused", NULL, 0, NULL },
286 { 991, "no-start-paused", "Start added torrents unpaused", NULL, 0, NULL },
287 { 992, "trash-torrent", "Delete torrents after adding", NULL, 0, NULL },
288 { 993, "no-trash-torrent", "Do not delete torrents after adding", NULL, 0, NULL },
289 { 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", 0, NULL },
290 { 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", 0, NULL },
291 { 'u', "uplimit", "Set the max upload speed in "SPEED_K_STR" for the current torrent(s) or globally", "u", 1, "<speed>" },
292 { 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", 0, NULL },
293 { 'v', "verify", "Verify the current torrent(s)", "v", 0, NULL },
294 { 'V', "version", "Show version number and exit", "V", 0, NULL },
295 { 'w', "download-dir", "When adding a new torrent, set its download folder. Otherwise, set the default download folder", "w", 1, "<path>" },
296 { 'x', "pex", "Enable peer exchange (PEX)", "x", 0, NULL },
297 { 'X', "no-pex", "Disable peer exchange (PEX)", "X", 0, NULL },
298 { 'y', "lpd", "Enable local peer discovery (LPD)", "y", 0, NULL },
299 { 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", 0, NULL },
300 { 941, "peer-info", "List the current torrent(s)' peers", "pi", 0, NULL },
301 { 0, NULL, NULL, NULL, 0, NULL }
304 static void
305 showUsage( void )
307 tr_getopt_usage( MY_NAME, getUsage( ), opts );
310 static int
311 numarg( const char * arg )
313 char * end = NULL;
314 const long num = strtol( arg, &end, 10 );
316 if( *end )
318 fprintf( stderr, "Not a number: \"%s\"\n", arg );
319 showUsage( );
320 exit( EXIT_FAILURE );
322 return num;
325 enum
327 MODE_TORRENT_START = (1<<0),
328 MODE_TORRENT_STOP = (1<<1),
329 MODE_TORRENT_VERIFY = (1<<2),
330 MODE_TORRENT_REANNOUNCE = (1<<3),
331 MODE_TORRENT_SET = (1<<4),
332 MODE_TORRENT_GET = (1<<5),
333 MODE_TORRENT_ADD = (1<<6),
334 MODE_TORRENT_REMOVE = (1<<7),
335 MODE_TORRENT_SET_LOCATION = (1<<8),
336 MODE_SESSION_SET = (1<<9),
337 MODE_SESSION_GET = (1<<10),
338 MODE_SESSION_STATS = (1<<11),
339 MODE_BLOCKLIST_UPDATE = (1<<12),
340 MODE_PORT_TEST = (1<<13)
343 static int
344 getOptMode( int val )
346 switch( val )
348 case TR_OPT_ERR:
349 case TR_OPT_UNK:
350 case 'a': /* add torrent */
351 case 'b': /* debug */
352 case 'n': /* auth */
353 case 'N': /* netrc */
354 case 't': /* set current torrent */
355 case 'V': /* show version number */
356 return 0;
358 case 'c': /* incomplete-dir */
359 case 'C': /* no-incomplete-dir */
360 case 'e': /* cache */
361 case 'm': /* portmap */
362 case 'M': /* "no-portmap */
363 case 'o': /* dht */
364 case 'O': /* no-dht */
365 case 'p': /* incoming peer port */
366 case 'P': /* random incoming peer port */
367 case 'x': /* pex */
368 case 'X': /* no-pex */
369 case 'y': /* lpd */
370 case 'Y': /* no-lpd */
371 case 800: /* torrent-done-script */
372 case 801: /* no-torrent-done-script */
373 case 970: /* alt-speed */
374 case 971: /* no-alt-speed */
375 case 972: /* alt-speed-downlimit */
376 case 973: /* alt-speed-uplimit */
377 case 974: /* alt-speed-scheduler */
378 case 975: /* no-alt-speed-scheduler */
379 case 976: /* alt-speed-time-begin */
380 case 977: /* alt-speed-time-end */
381 case 978: /* alt-speed-days */
382 case 910: /* encryption-required */
383 case 911: /* encryption-preferred */
384 case 912: /* encryption-tolerated */
385 case 953: /* global-seedratio */
386 case 954: /* no-global-seedratio */
387 case 990: /* start-paused */
388 case 991: /* no-start-paused */
389 case 992: /* trash-torrent */
390 case 993: /* no-trash-torrent */
391 return MODE_SESSION_SET;
393 case 712: /* tracker-remove */
394 case 950: /* seedratio */
395 case 951: /* seedratio-default */
396 case 952: /* no-seedratio */
397 case 984: /* honor-session */
398 case 985: /* no-honor-session */
399 return MODE_TORRENT_SET;
401 case 920: /* session-info */
402 return MODE_SESSION_GET;
404 case 'g': /* get */
405 case 'G': /* no-get */
406 case 700: /* torrent priority-high */
407 case 701: /* torrent priority-normal */
408 case 702: /* torrent priority-low */
409 case 710: /* tracker-add */
410 case 900: /* file priority-high */
411 case 901: /* file priority-normal */
412 case 902: /* file priority-low */
413 return MODE_TORRENT_SET | MODE_TORRENT_ADD;
415 case 961: /* find */
416 return MODE_TORRENT_SET_LOCATION | MODE_TORRENT_ADD;
418 case 'i': /* info */
419 case 'l': /* list all torrents */
420 case 940: /* info-files */
421 case 941: /* info-peer */
422 case 942: /* info-pieces */
423 case 943: /* info-tracker */
424 return MODE_TORRENT_GET;
426 case 'd': /* download speed limit */
427 case 'D': /* no download speed limit */
428 case 'u': /* upload speed limit */
429 case 'U': /* no upload speed limit */
430 case 930: /* peers */
431 return MODE_SESSION_SET | MODE_TORRENT_SET;
433 case 's': /* start */
434 return MODE_TORRENT_START | MODE_TORRENT_ADD;
436 case 'S': /* stop */
437 return MODE_TORRENT_STOP | MODE_TORRENT_ADD;
439 case 'w': /* download-dir */
440 return MODE_SESSION_SET | MODE_TORRENT_ADD;
442 case 963: /* blocklist-update */
443 return MODE_BLOCKLIST_UPDATE;
445 case 921: /* session-stats */
446 return MODE_SESSION_STATS;
448 case 'v': /* verify */
449 return MODE_TORRENT_VERIFY;
451 case 962: /* port-test */
452 return MODE_PORT_TEST;
454 case 'r': /* remove */
455 case 'R': /* remove and delete */
456 return MODE_TORRENT_REMOVE;
458 case 960: /* move */
459 return MODE_TORRENT_SET_LOCATION;
461 default:
462 fprintf( stderr, "unrecognized argument %d\n", val );
463 assert( "unrecognized argument" && 0 );
464 return 0;
468 static tr_bool debug = 0;
469 static char * auth = NULL;
470 static char * netrc = NULL;
471 static char * sessionId = NULL;
473 static char*
474 tr_getcwd( void )
476 char buf[2048];
477 *buf = '\0';
478 #ifdef WIN32
479 _getcwd( buf, sizeof( buf ) );
480 #else
481 getcwd( buf, sizeof( buf ) );
482 #endif
483 return tr_strdup( buf );
486 static char*
487 absolutify( const char * path )
489 char * buf;
491 if( *path == '/' )
492 buf = tr_strdup( path );
493 else {
494 char * cwd = tr_getcwd( );
495 buf = tr_buildPath( cwd, path, NULL );
496 tr_free( cwd );
499 return buf;
502 static char*
503 getEncodedMetainfo( const char * filename )
505 size_t len = 0;
506 char * b64 = NULL;
507 uint8_t * buf = tr_loadFile( filename, &len );
509 if( buf )
511 b64 = tr_base64_encode( buf, len, NULL );
512 tr_free( buf );
514 return b64;
517 static void
518 addIdArg( tr_benc * args, const char * id )
520 if( !*id )
522 fprintf(
523 stderr,
524 "No torrent specified! Please use the -t option first.\n" );
525 id = "-1"; /* no torrent will have this ID, so should be a no-op */
527 if( strcmp( id, "all" ) )
529 const char * pch;
530 tr_bool isList = strchr(id,',') || strchr(id,'-');
531 tr_bool isNum = TRUE;
532 for( pch=id; isNum && *pch; ++pch )
533 if( !isdigit( *pch ) )
534 isNum = FALSE;
535 if( isNum || isList )
536 tr_rpc_parse_list_str( tr_bencDictAdd( args, "ids" ), id, strlen( id ) );
537 else
538 tr_bencDictAddStr( args, "ids", id ); /* it's a torrent sha hash */
542 static void
543 addTime( tr_benc * args, const char * key, const char * arg )
545 int time;
546 tr_bool success = FALSE;
548 if( arg && ( strlen( arg ) == 4 ) )
550 const char hh[3] = { arg[0], arg[1], '\0' };
551 const char mm[3] = { arg[2], arg[3], '\0' };
552 const int hour = atoi( hh );
553 const int min = atoi( mm );
555 if( 0<=hour && hour<24 && 0<=min && min<60 )
557 time = min + ( hour * 60 );
558 success = TRUE;
562 if( success )
563 tr_bencDictAddInt( args, key, time );
564 else
565 fprintf( stderr, "Please specify the time of day in 'hhmm' format.\n" );
568 static void
569 addDays( tr_benc * args, const char * key, const char * arg )
571 int days = 0;
573 if( arg )
575 int i;
576 int valueCount;
577 int * values = tr_parseNumberRange( arg, -1, &valueCount );
578 for( i=0; i<valueCount; ++i )
580 if ( values[i] < 0 || values[i] > 7 ) continue;
581 if ( values[i] == 7 ) values[i] = 0;
583 days |= 1 << values[i];
585 tr_free( values );
588 if ( days )
589 tr_bencDictAddInt( args, key, days );
590 else
591 fprintf( stderr, "Please specify the days of the week in '1-3,4,7' format.\n" );
594 static void
595 addFiles( tr_benc * args,
596 const char * key,
597 const char * arg )
599 tr_benc * files = tr_bencDictAddList( args, key, 100 );
601 if( !*arg )
603 fprintf( stderr, "No files specified!\n" );
604 arg = "-1"; /* no file will have this index, so should be a no-op */
606 if( strcmp( arg, "all" ) )
608 int i;
609 int valueCount;
610 int * values = tr_parseNumberRange( arg, -1, &valueCount );
611 for( i=0; i<valueCount; ++i )
612 tr_bencListAddInt( files, values[i] );
613 tr_free( values );
617 #define TR_N_ELEMENTS( ary ) ( sizeof( ary ) / sizeof( *ary ) )
619 static const char * files_keys[] = {
620 "files",
621 "name",
622 "priorities",
623 "wanted"
626 static const char * details_keys[] = {
627 "activityDate",
628 "addedDate",
629 "bandwidthPriority",
630 "comment",
631 "corruptEver",
632 "creator",
633 "dateCreated",
634 "desiredAvailable",
635 "doneDate",
636 "downloadDir",
637 "downloadedEver",
638 "downloadLimit",
639 "downloadLimited",
640 "error",
641 "errorString",
642 "eta",
643 "hashString",
644 "haveUnchecked",
645 "haveValid",
646 "honorsSessionLimits",
647 "id",
648 "isFinished",
649 "isPrivate",
650 "leftUntilDone",
651 "name",
652 "peersConnected",
653 "peersGettingFromUs",
654 "peersSendingToUs",
655 "peer-limit",
656 "pieceCount",
657 "pieceSize",
658 "rateDownload",
659 "rateUpload",
660 "recheckProgress",
661 "seedRatioMode",
662 "seedRatioLimit",
663 "sizeWhenDone",
664 "startDate",
665 "status",
666 "totalSize",
667 "uploadedEver",
668 "uploadLimit",
669 "uploadLimited",
670 "webseeds",
671 "webseedsSendingToUs"
674 static const char * list_keys[] = {
675 "error",
676 "errorString",
677 "eta",
678 "id",
679 "isFinished",
680 "leftUntilDone",
681 "name",
682 "peersGettingFromUs",
683 "peersSendingToUs",
684 "rateDownload",
685 "rateUpload",
686 "sizeWhenDone",
687 "status",
688 "uploadRatio"
691 static size_t
692 writeFunc( void * ptr, size_t size, size_t nmemb, void * buf )
694 const size_t byteCount = size * nmemb;
695 evbuffer_add( buf, ptr, byteCount );
696 return byteCount;
699 /* look for a session id in the header in case the server gives back a 409 */
700 static size_t
701 parseResponseHeader( void *ptr, size_t size, size_t nmemb, void * stream UNUSED )
703 const char * line = ptr;
704 const size_t line_len = size * nmemb;
705 const char * key = TR_RPC_SESSION_ID_HEADER ": ";
706 const size_t key_len = strlen( key );
708 if( ( line_len >= key_len ) && !memcmp( line, key, key_len ) )
710 const char * begin = line + key_len;
711 const char * end = begin;
712 while( !isspace( *end ) )
713 ++end;
714 tr_free( sessionId );
715 sessionId = tr_strndup( begin, end-begin );
718 return line_len;
721 static long
722 getTimeoutSecs( const char * req )
724 if( strstr( req, "\"method\":\"blocklist-update\"" ) != NULL )
725 return 300L;
727 return 60L; /* default value */
730 static char*
731 getStatusString( tr_benc * t, char * buf, size_t buflen )
733 int64_t status;
734 tr_bool boolVal;
736 if( !tr_bencDictFindInt( t, "status", &status ) )
738 *buf = '\0';
740 else switch( status )
742 case TR_STATUS_STOPPED:
743 if( tr_bencDictFindBool( t, "isFinished", &boolVal ) && boolVal )
744 tr_strlcpy( buf, "Finished", buflen );
745 else
746 tr_strlcpy( buf, "Stopped", buflen );
747 break;
749 case TR_STATUS_CHECK_WAIT:
750 case TR_STATUS_CHECK: {
751 const char * str = status == TR_STATUS_CHECK_WAIT
752 ? "Will Verify"
753 : "Verifying";
754 double percent;
755 if( tr_bencDictFindReal( t, "recheckProgress", &percent ) )
756 tr_snprintf( buf, buflen, "%s (%.0f%%)", str, floor(percent*100.0) );
757 else
758 tr_strlcpy( buf, str, buflen );
760 break;
763 case TR_STATUS_DOWNLOAD:
764 case TR_STATUS_SEED: {
765 int64_t fromUs = 0;
766 int64_t toUs = 0;
767 tr_bencDictFindInt( t, "peersGettingFromUs", &fromUs );
768 tr_bencDictFindInt( t, "peersSendingToUs", &toUs );
769 if( fromUs && toUs )
770 tr_strlcpy( buf, "Up & Down", buflen );
771 else if( toUs )
772 tr_strlcpy( buf, "Downloading", buflen );
773 else if( fromUs ) {
774 int64_t leftUntilDone = 0;
775 tr_bencDictFindInt( t, "leftUntilDone", &leftUntilDone );
776 if( leftUntilDone > 0 )
777 tr_strlcpy( buf, "Uploading", buflen );
778 else
779 tr_strlcpy( buf, "Seeding", buflen );
780 } else {
781 tr_strlcpy( buf, "Idle", buflen );
783 break;
787 return buf;
790 static const char *bandwidthPriorityNames[] =
791 { "Low", "Normal", "High", "Invalid" };
793 static void
794 printDetails( tr_benc * top )
796 tr_benc *args, *torrents;
798 if( ( tr_bencDictFindDict( top, "arguments", &args ) )
799 && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
801 int ti, tCount;
802 for( ti = 0, tCount = tr_bencListSize( torrents ); ti < tCount;
803 ++ti )
805 tr_benc * t = tr_bencListChild( torrents, ti );
806 tr_benc * l;
807 const char * str;
808 char buf[512];
809 char buf2[512];
810 int64_t i, j, k;
811 tr_bool boolVal;
812 double d;
814 printf( "NAME\n" );
815 if( tr_bencDictFindInt( t, "id", &i ) )
816 printf( " Id: %" PRId64 "\n", i );
817 if( tr_bencDictFindStr( t, "name", &str ) )
818 printf( " Name: %s\n", str );
819 if( tr_bencDictFindStr( t, "hashString", &str ) )
820 printf( " Hash: %s\n", str );
821 printf( "\n" );
823 printf( "TRANSFER\n" );
824 getStatusString( t, buf, sizeof( buf ) );
825 printf( " State: %s\n", buf );
827 if( tr_bencDictFindStr( t, "downloadDir", &str ) )
828 printf( " Location: %s\n", str );
830 if( tr_bencDictFindInt( t, "sizeWhenDone", &i )
831 && tr_bencDictFindInt( t, "leftUntilDone", &j ) )
833 strlpercent( buf, 100.0 * ( i - j ) / i, sizeof( buf ) );
834 printf( " Percent Done: %s%%\n", buf );
837 if( tr_bencDictFindInt( t, "eta", &i ) )
838 printf( " ETA: %s\n", tr_strltime( buf, i, sizeof( buf ) ) );
839 if( tr_bencDictFindInt( t, "rateDownload", &i ) )
840 printf( " Download Speed: %s\n", tr_formatter_speed_KBps( buf, i/(double)tr_speed_K, sizeof( buf ) ) );
841 if( tr_bencDictFindInt( t, "rateUpload", &i ) )
842 printf( " Upload Speed: %s\n", tr_formatter_speed_KBps( buf, i/(double)tr_speed_K, sizeof( buf ) ) );
843 if( tr_bencDictFindInt( t, "haveUnchecked", &i )
844 && tr_bencDictFindInt( t, "haveValid", &j ) )
846 strlsize( buf, i + j, sizeof( buf ) );
847 strlsize( buf2, j, sizeof( buf2 ) );
848 printf( " Have: %s (%s verified)\n", buf, buf2 );
851 if( tr_bencDictFindInt( t, "sizeWhenDone", &i ) )
853 if( i < 1 )
854 printf( " Availability: None\n" );
855 if( tr_bencDictFindInt( t, "desiredAvailable", &j)
856 && tr_bencDictFindInt( t, "leftUntilDone", &k) )
858 j += i - k;
859 strlpercent( buf, 100.0 * j / i, sizeof( buf ) );
860 printf( " Availability: %s%%\n", buf );
862 if( tr_bencDictFindInt( t, "totalSize", &j ) )
864 strlsize( buf2, i, sizeof( buf2 ) );
865 strlsize( buf, j, sizeof( buf ) );
866 printf( " Total size: %s (%s wanted)\n", buf, buf2 );
869 if( tr_bencDictFindInt( t, "downloadedEver", &i )
870 && tr_bencDictFindInt( t, "uploadedEver", &j ) )
872 strlsize( buf, i, sizeof( buf ) );
873 printf( " Downloaded: %s\n", buf );
874 strlsize( buf, j, sizeof( buf ) );
875 printf( " Uploaded: %s\n", buf );
876 strlratio( buf, j, i, sizeof( buf ) );
877 printf( " Ratio: %s\n", buf );
879 if( tr_bencDictFindInt( t, "seedRatioMode", &i))
881 switch( i ) {
882 case TR_RATIOLIMIT_GLOBAL:
883 printf( " Ratio Limit: Default\n" );
884 break;
885 case TR_RATIOLIMIT_SINGLE:
886 if( tr_bencDictFindReal( t, "seedRatioLimit", &d))
887 printf( " Ratio Limit: %.2f\n", d );
888 break;
889 case TR_RATIOLIMIT_UNLIMITED:
890 printf( " Ratio Limit: Unlimited\n" );
891 break;
892 default: break;
895 if( tr_bencDictFindInt( t, "corruptEver", &i ) )
897 strlsize( buf, i, sizeof( buf ) );
898 printf( " Corrupt DL: %s\n", buf );
900 if( tr_bencDictFindStr( t, "errorString", &str ) && str && *str &&
901 tr_bencDictFindInt( t, "error", &i ) && i )
903 switch( i ) {
904 case TR_STAT_TRACKER_WARNING: printf( " Tracker gave a warning: %s\n", str ); break;
905 case TR_STAT_TRACKER_ERROR: printf( " Tracker gave an error: %s\n", str ); break;
906 case TR_STAT_LOCAL_ERROR: printf( " Error: %s\n", str ); break;
907 default: break; /* no error */
910 if( tr_bencDictFindInt( t, "peersConnected", &i )
911 && tr_bencDictFindInt( t, "peersGettingFromUs", &j )
912 && tr_bencDictFindInt( t, "peersSendingToUs", &k ) )
914 printf(
915 " Peers: "
916 "connected to %" PRId64 ", "
917 "uploading to %" PRId64
918 ", "
919 "downloading from %"
920 PRId64 "\n",
921 i, j, k );
924 if( tr_bencDictFindList( t, "webseeds", &l )
925 && tr_bencDictFindInt( t, "webseedsSendingToUs", &i ) )
927 const int64_t n = tr_bencListSize( l );
928 if( n > 0 )
929 printf(
930 " Web Seeds: downloading from %" PRId64 " of %"
931 PRId64
932 " web seeds\n", i, n );
934 printf( "\n" );
936 printf( "HISTORY\n" );
937 if( tr_bencDictFindInt( t, "addedDate", &i ) && i )
939 const time_t tt = i;
940 printf( " Date added: %s", ctime( &tt ) );
942 if( tr_bencDictFindInt( t, "doneDate", &i ) && i )
944 const time_t tt = i;
945 printf( " Date finished: %s", ctime( &tt ) );
947 if( tr_bencDictFindInt( t, "startDate", &i ) && i )
949 const time_t tt = i;
950 printf( " Date started: %s", ctime( &tt ) );
952 if( tr_bencDictFindInt( t, "activityDate", &i ) && i )
954 const time_t tt = i;
955 printf( " Latest activity: %s", ctime( &tt ) );
957 printf( "\n" );
959 printf( "ORIGINS\n" );
960 if( tr_bencDictFindInt( t, "dateCreated", &i ) && i )
962 const time_t tt = i;
963 printf( " Date created: %s", ctime( &tt ) );
965 if( tr_bencDictFindBool( t, "isPrivate", &boolVal ) )
966 printf( " Public torrent: %s\n", ( boolVal ? "No" : "Yes" ) );
967 if( tr_bencDictFindStr( t, "comment", &str ) && str && *str )
968 printf( " Comment: %s\n", str );
969 if( tr_bencDictFindStr( t, "creator", &str ) && str && *str )
970 printf( " Creator: %s\n", str );
971 if( tr_bencDictFindInt( t, "pieceCount", &i ) )
972 printf( " Piece Count: %" PRId64 "\n", i );
973 if( tr_bencDictFindInt( t, "pieceSize", &i ) )
974 printf( " Piece Size: %s\n", strlmem( buf, i, sizeof( buf ) ) );
975 printf( "\n" );
977 printf( "LIMITS & BANDWIDTH\n" );
978 if( tr_bencDictFindBool( t, "downloadLimited", &boolVal )
979 && tr_bencDictFindInt( t, "downloadLimit", &i ) )
981 printf( " Download Limit: " );
982 if( boolVal )
983 printf( "%s\n", tr_formatter_speed_KBps( buf, i, sizeof( buf ) ) );
984 else
985 printf( "Unlimited\n" );
987 if( tr_bencDictFindBool( t, "uploadLimited", &boolVal )
988 && tr_bencDictFindInt( t, "uploadLimit", &i ) )
990 printf( " Upload Limit: " );
991 if( boolVal )
992 printf( "%s\n", tr_formatter_speed_KBps( buf, i, sizeof( buf ) ) );
993 else
994 printf( "Unlimited\n" );
996 if( tr_bencDictFindBool( t, "honorsSessionLimits", &boolVal ) )
997 printf( " Honors Session Limits: %s\n", ( boolVal ? "Yes" : "No" ) );
998 if( tr_bencDictFindInt ( t, "peer-limit", &i ) )
999 printf( " Peer limit: %" PRId64 "\n", i );
1000 if (tr_bencDictFindInt (t, "bandwidthPriority", &i))
1001 printf (" Bandwidth Priority: %s\n",
1002 bandwidthPriorityNames[(i + 1) & 3]);
1004 printf( "\n" );
1009 static void
1010 printFileList( tr_benc * top )
1012 tr_benc *args, *torrents;
1014 if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1015 && ( tr_bencDictFindList( args, "torrents", &torrents ) ) )
1017 int i, in;
1018 for( i = 0, in = tr_bencListSize( torrents ); i < in; ++i )
1020 tr_benc * d = tr_bencListChild( torrents, i );
1021 tr_benc * files, *priorities, *wanteds;
1022 const char * name;
1023 if( tr_bencDictFindStr( d, "name", &name )
1024 && tr_bencDictFindList( d, "files", &files )
1025 && tr_bencDictFindList( d, "priorities", &priorities )
1026 && tr_bencDictFindList( d, "wanted", &wanteds ) )
1028 int j = 0, jn = tr_bencListSize( files );
1029 printf( "%s (%d files):\n", name, jn );
1030 printf( "%3s %4s %8s %3s %9s %s\n", "#", "Done",
1031 "Priority", "Get", "Size",
1032 "Name" );
1033 for( j = 0, jn = tr_bencListSize( files ); j < jn; ++j )
1035 int64_t have;
1036 int64_t length;
1037 int64_t priority;
1038 int64_t wanted;
1039 const char * filename;
1040 tr_benc * file = tr_bencListChild( files, j );
1041 if( tr_bencDictFindInt( file, "length", &length )
1042 && tr_bencDictFindStr( file, "name", &filename )
1043 && tr_bencDictFindInt( file, "bytesCompleted", &have )
1044 && tr_bencGetInt( tr_bencListChild( priorities,
1045 j ), &priority )
1046 && tr_bencGetInt( tr_bencListChild( wanteds,
1047 j ), &wanted ) )
1049 char sizestr[64];
1050 double percent = (double)have / length;
1051 const char * pristr;
1052 strlsize( sizestr, length, sizeof( sizestr ) );
1053 switch( priority )
1055 case TR_PRI_LOW:
1056 pristr = "Low"; break;
1058 case TR_PRI_HIGH:
1059 pristr = "High"; break;
1061 default:
1062 pristr = "Normal"; break;
1064 printf( "%3d: %3.0f%% %-8s %-3s %9s %s\n",
1066 floor( 100.0 * percent ),
1067 pristr,
1068 ( wanted ? "Yes" : "No" ),
1069 sizestr,
1070 filename );
1078 static void
1079 printPeersImpl( tr_benc * peers )
1081 int i, n;
1082 printf( "%-20s %-12s %-5s %-6s %-6s %s\n",
1083 "Address", "Flags", "Done", "Down", "Up", "Client" );
1084 for( i = 0, n = tr_bencListSize( peers ); i < n; ++i )
1086 double progress;
1087 const char * address, * client, * flagstr;
1088 int64_t rateToClient, rateToPeer;
1089 tr_benc * d = tr_bencListChild( peers, i );
1091 if( tr_bencDictFindStr( d, "address", &address )
1092 && tr_bencDictFindStr( d, "clientName", &client )
1093 && tr_bencDictFindReal( d, "progress", &progress )
1094 && tr_bencDictFindStr( d, "flagStr", &flagstr )
1095 && tr_bencDictFindInt( d, "rateToClient", &rateToClient )
1096 && tr_bencDictFindInt( d, "rateToPeer", &rateToPeer ) )
1098 printf( "%-20s %-12s %-5.1f %6.1f %6.1f %s\n",
1099 address, flagstr, (progress*100.0),
1100 rateToClient / (double)tr_speed_K,
1101 rateToPeer / (double)tr_speed_K,
1102 client );
1107 static void
1108 printPeers( tr_benc * top )
1110 tr_benc *args, *torrents;
1112 if( tr_bencDictFindDict( top, "arguments", &args )
1113 && tr_bencDictFindList( args, "torrents", &torrents ) )
1115 int i, n;
1116 for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1118 tr_benc * peers;
1119 tr_benc * torrent = tr_bencListChild( torrents, i );
1120 if( tr_bencDictFindList( torrent, "peers", &peers ) ) {
1121 printPeersImpl( peers );
1122 if( i+1<n )
1123 printf( "\n" );
1129 static void
1130 printPiecesImpl( const uint8_t * raw, size_t rawlen, int64_t j )
1132 int i, k, len;
1133 char * str = tr_base64_decode( raw, rawlen, &len );
1134 printf( " " );
1135 for( i=k=0; k<len; ++k ) {
1136 int e;
1137 for( e=0; i<j && e<8; ++e, ++i )
1138 printf( "%c", str[k] & (1<<(7-e)) ? '1' : '0' );
1139 printf( " " );
1140 if( !(i%64) )
1141 printf( "\n " );
1143 printf( "\n" );
1144 tr_free( str );
1147 static void
1148 printPieces( tr_benc * top )
1150 tr_benc *args, *torrents;
1152 if( tr_bencDictFindDict( top, "arguments", &args )
1153 && tr_bencDictFindList( args, "torrents", &torrents ) )
1155 int i, n;
1156 for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1158 int64_t j;
1159 const uint8_t * raw;
1160 size_t rawlen;
1161 tr_benc * torrent = tr_bencListChild( torrents, i );
1162 if( tr_bencDictFindRaw( torrent, "pieces", &raw, &rawlen ) &&
1163 tr_bencDictFindInt( torrent, "pieceCount", &j ) ) {
1164 printPiecesImpl( raw, rawlen, j );
1165 if( i+1<n )
1166 printf( "\n" );
1172 static void
1173 printPortTest( tr_benc * top )
1175 tr_benc *args;
1176 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1178 tr_bool boolVal;
1180 if( tr_bencDictFindBool( args, "port-is-open", &boolVal ) )
1181 printf( "Port is open: %s\n", ( boolVal ? "Yes" : "No" ) );
1185 static void
1186 printTorrentList( tr_benc * top )
1188 tr_benc *args, *list;
1190 if( ( tr_bencDictFindDict( top, "arguments", &args ) )
1191 && ( tr_bencDictFindList( args, "torrents", &list ) ) )
1193 int i, n;
1194 int64_t total_size=0;
1195 double total_up=0, total_down=0;
1196 char haveStr[32];
1198 printf( "%-4s %-4s %9s %-8s %6s %6s %-5s %-11s %s\n",
1199 "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio", "Status",
1200 "Name" );
1202 for( i = 0, n = tr_bencListSize( list ); i < n; ++i )
1204 int64_t id, eta, status, up, down;
1205 int64_t sizeWhenDone, leftUntilDone;
1206 double ratio;
1207 const char * name;
1208 tr_benc * d = tr_bencListChild( list, i );
1209 if( tr_bencDictFindInt( d, "eta", &eta )
1210 && tr_bencDictFindInt( d, "id", &id )
1211 && tr_bencDictFindInt( d, "leftUntilDone", &leftUntilDone )
1212 && tr_bencDictFindStr( d, "name", &name )
1213 && tr_bencDictFindInt( d, "rateDownload", &down )
1214 && tr_bencDictFindInt( d, "rateUpload", &up )
1215 && tr_bencDictFindInt( d, "sizeWhenDone", &sizeWhenDone )
1216 && tr_bencDictFindInt( d, "status", &status )
1217 && tr_bencDictFindReal( d, "uploadRatio", &ratio ) )
1219 char etaStr[16];
1220 char statusStr[64];
1221 char ratioStr[32];
1222 char doneStr[8];
1223 int64_t error;
1224 char errorMark;
1226 if( sizeWhenDone )
1227 tr_snprintf( doneStr, sizeof( doneStr ), "%d%%", (int)( 100.0 * ( sizeWhenDone - leftUntilDone ) / sizeWhenDone ) );
1228 else
1229 tr_strlcpy( doneStr, "n/a", sizeof( doneStr ) );
1231 strlsize( haveStr, sizeWhenDone - leftUntilDone, sizeof( haveStr ) );
1233 if( leftUntilDone || eta != -1 )
1234 etaToString( etaStr, sizeof( etaStr ), eta );
1235 else
1236 tr_snprintf( etaStr, sizeof( etaStr ), "Done" );
1237 if( tr_bencDictFindInt( d, "error", &error ) && error )
1238 errorMark = '*';
1239 else
1240 errorMark = ' ';
1241 printf(
1242 "%4d%c %4s %9s %-8s %6.1f %6.1f %5s %-11s %s\n",
1243 (int)id, errorMark,
1244 doneStr,
1245 haveStr,
1246 etaStr,
1247 up/(double)tr_speed_K,
1248 down/(double)tr_speed_K,
1249 strlratio2( ratioStr, ratio, sizeof( ratioStr ) ),
1250 getStatusString( d, statusStr, sizeof( statusStr ) ),
1251 name );
1253 total_up += up;
1254 total_down += down;
1255 total_size += sizeWhenDone - leftUntilDone;
1259 printf( "Sum: %9s %6.1f %6.1f\n",
1260 strlsize( haveStr, total_size, sizeof( haveStr ) ),
1261 total_up/(double)tr_speed_K,
1262 total_down/(double)tr_speed_K );
1266 static void
1267 printTrackersImpl( tr_benc * trackerStats )
1269 int i;
1270 char buf[512];
1271 tr_benc * t;
1273 for( i=0; (( t = tr_bencListChild( trackerStats, i ))); ++i )
1275 int64_t downloadCount;
1276 tr_bool hasAnnounced;
1277 tr_bool hasScraped;
1278 const char * host;
1279 int64_t id;
1280 tr_bool isBackup;
1281 int64_t lastAnnouncePeerCount;
1282 const char * lastAnnounceResult;
1283 int64_t lastAnnounceStartTime;
1284 tr_bool lastAnnounceSucceeded;
1285 int64_t lastAnnounceTime;
1286 tr_bool lastAnnounceTimedOut;
1287 const char * lastScrapeResult;
1288 tr_bool lastScrapeSucceeded;
1289 int64_t lastScrapeStartTime;
1290 int64_t lastScrapeTime;
1291 tr_bool lastScrapeTimedOut;
1292 int64_t leecherCount;
1293 int64_t nextAnnounceTime;
1294 int64_t nextScrapeTime;
1295 int64_t seederCount;
1296 int64_t tier;
1297 int64_t announceState;
1298 int64_t scrapeState;
1300 if( tr_bencDictFindInt ( t, "downloadCount", &downloadCount ) &&
1301 tr_bencDictFindBool( t, "hasAnnounced", &hasAnnounced ) &&
1302 tr_bencDictFindBool( t, "hasScraped", &hasScraped ) &&
1303 tr_bencDictFindStr ( t, "host", &host ) &&
1304 tr_bencDictFindInt ( t, "id", &id ) &&
1305 tr_bencDictFindBool( t, "isBackup", &isBackup ) &&
1306 tr_bencDictFindInt ( t, "announceState", &announceState ) &&
1307 tr_bencDictFindInt ( t, "scrapeState", &scrapeState ) &&
1308 tr_bencDictFindInt ( t, "lastAnnouncePeerCount", &lastAnnouncePeerCount ) &&
1309 tr_bencDictFindStr ( t, "lastAnnounceResult", &lastAnnounceResult ) &&
1310 tr_bencDictFindInt ( t, "lastAnnounceStartTime", &lastAnnounceStartTime ) &&
1311 tr_bencDictFindBool( t, "lastAnnounceSucceeded", &lastAnnounceSucceeded ) &&
1312 tr_bencDictFindInt ( t, "lastAnnounceTime", &lastAnnounceTime ) &&
1313 tr_bencDictFindBool( t, "lastAnnounceTimedOut", &lastAnnounceTimedOut ) &&
1314 tr_bencDictFindStr ( t, "lastScrapeResult", &lastScrapeResult ) &&
1315 tr_bencDictFindInt ( t, "lastScrapeStartTime", &lastScrapeStartTime ) &&
1316 tr_bencDictFindBool( t, "lastScrapeSucceeded", &lastScrapeSucceeded ) &&
1317 tr_bencDictFindInt ( t, "lastScrapeTime", &lastScrapeTime ) &&
1318 tr_bencDictFindBool( t, "lastScrapeTimedOut", &lastScrapeTimedOut ) &&
1319 tr_bencDictFindInt ( t, "leecherCount", &leecherCount ) &&
1320 tr_bencDictFindInt ( t, "nextAnnounceTime", &nextAnnounceTime ) &&
1321 tr_bencDictFindInt ( t, "nextScrapeTime", &nextScrapeTime ) &&
1322 tr_bencDictFindInt ( t, "seederCount", &seederCount ) &&
1323 tr_bencDictFindInt ( t, "tier", &tier ) )
1325 const time_t now = time( NULL );
1327 printf( "\n" );
1328 printf( " Tracker %d: %s\n", (int)(id), host );
1329 if( isBackup )
1330 printf( " Backup on tier %d\n", (int)tier );
1331 else
1332 printf( " Active in tier %d\n", (int)tier );
1334 if( !isBackup )
1336 if( hasAnnounced && announceState != TR_TRACKER_INACTIVE )
1338 tr_strltime( buf, now - lastAnnounceTime, sizeof( buf ) );
1339 if( lastAnnounceSucceeded )
1340 printf( " Got a list of %'d peers %s ago\n",
1341 (int)lastAnnouncePeerCount, buf );
1342 else if( lastAnnounceTimedOut )
1343 printf( " Peer list request timed out; will retry\n" );
1344 else
1345 printf( " Got an error \"%s\" %s ago\n",
1346 lastAnnounceResult, buf );
1349 switch( announceState )
1351 case TR_TRACKER_INACTIVE:
1352 printf( " No updates scheduled\n" );
1353 break;
1354 case TR_TRACKER_WAITING:
1355 tr_strltime( buf, nextAnnounceTime - now, sizeof( buf ) );
1356 printf( " Asking for more peers in %s\n", buf );
1357 break;
1358 case TR_TRACKER_QUEUED:
1359 printf( " Queued to ask for more peers\n" );
1360 break;
1361 case TR_TRACKER_ACTIVE:
1362 tr_strltime( buf, now - lastAnnounceStartTime, sizeof( buf ) );
1363 printf( " Asking for more peers now... %s\n", buf );
1364 break;
1367 if( hasScraped )
1369 tr_strltime( buf, now - lastScrapeTime, sizeof( buf ) );
1370 if( lastScrapeSucceeded )
1371 printf( " Tracker had %'d seeders and %'d leechers %s ago\n",
1372 (int)seederCount, (int)leecherCount, buf );
1373 else if( lastScrapeTimedOut )
1374 printf( " Tracker scrape timed out; will retry\n" );
1375 else
1376 printf( " Got a scrape error \"%s\" %s ago\n",
1377 lastScrapeResult, buf );
1380 switch( scrapeState )
1382 case TR_TRACKER_INACTIVE:
1383 break;
1384 case TR_TRACKER_WAITING:
1385 tr_strltime( buf, nextScrapeTime - now, sizeof( buf ) );
1386 printf( " Asking for peer counts in %s\n", buf );
1387 break;
1388 case TR_TRACKER_QUEUED:
1389 printf( " Queued to ask for peer counts\n" );
1390 break;
1391 case TR_TRACKER_ACTIVE:
1392 tr_strltime( buf, now - lastScrapeStartTime, sizeof( buf ) );
1393 printf( " Asking for peer counts now... %s\n", buf );
1394 break;
1401 static void
1402 printTrackers( tr_benc * top )
1404 tr_benc *args, *torrents;
1406 if( tr_bencDictFindDict( top, "arguments", &args )
1407 && tr_bencDictFindList( args, "torrents", &torrents ) )
1409 int i, n;
1410 for( i=0, n=tr_bencListSize( torrents ); i<n; ++i )
1412 tr_benc * trackerStats;
1413 tr_benc * torrent = tr_bencListChild( torrents, i );
1414 if( tr_bencDictFindList( torrent, "trackerStats", &trackerStats ) ) {
1415 printTrackersImpl( trackerStats );
1416 if( i+1<n )
1417 printf( "\n" );
1423 static void
1424 printSession( tr_benc * top )
1426 tr_benc *args;
1427 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1429 int64_t i;
1430 char buf[64];
1431 tr_bool boolVal;
1432 const char * str;
1434 printf( "VERSION\n" );
1435 if( tr_bencDictFindStr( args, "version", &str ) )
1436 printf( " Daemon version: %s\n", str );
1437 if( tr_bencDictFindInt( args, "rpc-version", &i ) )
1438 printf( " RPC version: %" PRId64 "\n", i );
1439 if( tr_bencDictFindInt( args, "rpc-version-minimum", &i ) )
1440 printf( " RPC minimum version: %" PRId64 "\n", i );
1441 printf( "\n" );
1443 printf( "CONFIG\n" );
1444 if( tr_bencDictFindStr( args, "config-dir", &str ) )
1445 printf( " Configuration directory: %s\n", str );
1446 if( tr_bencDictFindStr( args, TR_PREFS_KEY_DOWNLOAD_DIR, &str ) )
1447 printf( " Download directory: %s\n", str );
1448 if( tr_bencDictFindInt( args, TR_PREFS_KEY_PEER_PORT, &i ) )
1449 printf( " Listenport: %" PRId64 "\n", i );
1450 if( tr_bencDictFindBool( args, TR_PREFS_KEY_PORT_FORWARDING, &boolVal ) )
1451 printf( " Portforwarding enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1452 if( tr_bencDictFindBool( args, TR_PREFS_KEY_DHT_ENABLED, &boolVal ) )
1453 printf( " Distributed hash table enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1454 if( tr_bencDictFindBool( args, TR_PREFS_KEY_LPD_ENABLED, &boolVal ) )
1455 printf( " Local peer discovery enabled: %s\n", ( boolVal ? "Yes" : "No" ) );
1456 if( tr_bencDictFindBool( args, TR_PREFS_KEY_PEX_ENABLED, &boolVal ) )
1457 printf( " Peer exchange allowed: %s\n", ( boolVal ? "Yes" : "No" ) );
1458 if( tr_bencDictFindStr( args, TR_PREFS_KEY_ENCRYPTION, &str ) )
1459 printf( " Encryption: %s\n", str );
1460 if( tr_bencDictFindInt( args, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, &i ) )
1461 printf( " Maximum memory cache size: %s\n", tr_formatter_mem_MB( buf, i, sizeof( buf ) ) );
1462 printf( "\n" );
1465 tr_bool altEnabled, altTimeEnabled, upEnabled, downEnabled, seedRatioLimited;
1466 int64_t altDown, altUp, altBegin, altEnd, altDay, upLimit, downLimit, peerLimit;
1467 double seedRatioLimit;
1469 if( tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, &altDown ) &&
1470 tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, &altEnabled ) &&
1471 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, &altBegin ) &&
1472 tr_bencDictFindBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, &altTimeEnabled ) &&
1473 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, &altEnd ) &&
1474 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, &altDay ) &&
1475 tr_bencDictFindInt ( args, TR_PREFS_KEY_ALT_SPEED_UP_KBps, &altUp ) &&
1476 tr_bencDictFindInt ( args, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, &peerLimit ) &&
1477 tr_bencDictFindInt ( args, TR_PREFS_KEY_DSPEED_KBps, &downLimit ) &&
1478 tr_bencDictFindBool( args, TR_PREFS_KEY_DSPEED_ENABLED, &downEnabled ) &&
1479 tr_bencDictFindInt ( args, TR_PREFS_KEY_USPEED_KBps, &upLimit ) &&
1480 tr_bencDictFindBool( args, TR_PREFS_KEY_USPEED_ENABLED, &upEnabled ) &&
1481 tr_bencDictFindReal( args, "seedRatioLimit", &seedRatioLimit ) &&
1482 tr_bencDictFindBool( args, "seedRatioLimited", &seedRatioLimited) )
1484 char buf[128];
1485 char buf2[128];
1486 char buf3[128];
1488 printf( "LIMITS\n" );
1489 printf( " Peer limit: %" PRId64 "\n", peerLimit );
1491 if( seedRatioLimited )
1492 tr_snprintf( buf, sizeof( buf ), "%.2f", seedRatioLimit );
1493 else
1494 tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1495 printf( " Default seed ratio limit: %s\n", buf );
1497 if( altEnabled )
1498 tr_formatter_speed_KBps( buf, altUp, sizeof( buf ) );
1499 else if( upEnabled )
1500 tr_formatter_speed_KBps( buf, upLimit, sizeof( buf ) );
1501 else
1502 tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1503 printf( " Upload speed limit: %s (%s limit: %s; %s turtle limit: %s)\n",
1504 buf,
1505 upEnabled ? "Enabled" : "Disabled",
1506 tr_formatter_speed_KBps( buf2, upLimit, sizeof( buf2 ) ),
1507 altEnabled ? "Enabled" : "Disabled",
1508 tr_formatter_speed_KBps( buf3, altUp, sizeof( buf3 ) ) );
1510 if( altEnabled )
1511 tr_formatter_speed_KBps( buf, altDown, sizeof( buf ) );
1512 else if( downEnabled )
1513 tr_formatter_speed_KBps( buf, downLimit, sizeof( buf ) );
1514 else
1515 tr_strlcpy( buf, "Unlimited", sizeof( buf ) );
1516 printf( " Download speed limit: %s (%s limit: %s; %s turtle limit: %s)\n",
1517 buf,
1518 downEnabled ? "Enabled" : "Disabled",
1519 tr_formatter_speed_KBps( buf2, downLimit, sizeof( buf2 ) ),
1520 altEnabled ? "Enabled" : "Disabled",
1521 tr_formatter_speed_KBps( buf2, altDown, sizeof( buf2 ) ) );
1523 if( altTimeEnabled ) {
1524 printf( " Turtle schedule: %02d:%02d - %02d:%02d ",
1525 (int)(altBegin/60), (int)(altBegin%60),
1526 (int)(altEnd/60), (int)(altEnd%60) );
1527 if( altDay & TR_SCHED_SUN ) printf( "Sun " );
1528 if( altDay & TR_SCHED_MON ) printf( "Mon " );
1529 if( altDay & TR_SCHED_TUES ) printf( "Tue " );
1530 if( altDay & TR_SCHED_WED ) printf( "Wed " );
1531 if( altDay & TR_SCHED_THURS ) printf( "Thu " );
1532 if( altDay & TR_SCHED_FRI ) printf( "Fri " );
1533 if( altDay & TR_SCHED_SAT ) printf( "Sat " );
1534 printf( "\n" );
1538 printf( "\n" );
1540 printf( "MISC\n" );
1541 if( tr_bencDictFindBool( args, TR_PREFS_KEY_START, &boolVal ) )
1542 printf( " Autostart added torrents: %s\n", ( boolVal ? "Yes" : "No" ) );
1543 if( tr_bencDictFindBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, &boolVal ) )
1544 printf( " Delete automatically added torrents: %s\n", ( boolVal ? "Yes" : "No" ) );
1548 static void
1549 printSessionStats( tr_benc * top )
1551 tr_benc *args, *d;
1552 if( ( tr_bencDictFindDict( top, "arguments", &args ) ) )
1554 char buf[512];
1555 int64_t up, down, secs, sessions;
1557 if( tr_bencDictFindDict( args, "current-stats", &d )
1558 && tr_bencDictFindInt( d, "uploadedBytes", &up )
1559 && tr_bencDictFindInt( d, "downloadedBytes", &down )
1560 && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1562 printf( "\nCURRENT SESSION\n" );
1563 printf( " Uploaded: %s\n", strlsize( buf, up, sizeof( buf ) ) );
1564 printf( " Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1565 printf( " Ratio: %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1566 printf( " Duration: %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1569 if( tr_bencDictFindDict( args, "cumulative-stats", &d )
1570 && tr_bencDictFindInt( d, "sessionCount", &sessions )
1571 && tr_bencDictFindInt( d, "uploadedBytes", &up )
1572 && tr_bencDictFindInt( d, "downloadedBytes", &down )
1573 && tr_bencDictFindInt( d, "secondsActive", &secs ) )
1575 printf( "\nTOTAL\n" );
1576 printf( " Started %lu times\n", (unsigned long)sessions );
1577 printf( " Uploaded: %s\n", strlsize( buf, up, sizeof( buf ) ) );
1578 printf( " Downloaded: %s\n", strlsize( buf, down, sizeof( buf ) ) );
1579 printf( " Ratio: %s\n", strlratio( buf, up, down, sizeof( buf ) ) );
1580 printf( " Duration: %s\n", tr_strltime( buf, secs, sizeof( buf ) ) );
1585 static char id[4096];
1587 static int
1588 processResponse( const char * host, int port, const void * response, size_t len )
1590 tr_benc top;
1591 int status = EXIT_SUCCESS;
1593 if( debug )
1594 fprintf( stderr, "got response (len %d):\n--------\n%*.*s\n--------\n",
1595 (int)len, (int)len, (int)len, (const char*) response );
1597 if( tr_jsonParse( NULL, response, len, &top, NULL ) )
1599 tr_nerr( MY_NAME, "Unable to parse response \"%*.*s\"", (int)len,
1600 (int)len, (char*)response );
1601 status |= EXIT_FAILURE;
1603 else
1605 int64_t tag = -1;
1606 const char * str;
1607 tr_bencDictFindInt( &top, "tag", &tag );
1609 switch( tag )
1611 case TAG_SESSION:
1612 printSession( &top ); break;
1614 case TAG_STATS:
1615 printSessionStats( &top ); break;
1617 case TAG_DETAILS:
1618 printDetails( &top ); break;
1620 case TAG_FILES:
1621 printFileList( &top ); break;
1623 case TAG_LIST:
1624 printTorrentList( &top ); break;
1626 case TAG_PEERS:
1627 printPeers( &top ); break;
1629 case TAG_PIECES:
1630 printPieces( &top ); break;
1632 case TAG_PORTTEST:
1633 printPortTest( &top ); break;
1635 case TAG_TRACKERS:
1636 printTrackers( &top ); break;
1638 case TAG_TORRENT_ADD: {
1639 int64_t i;
1640 tr_benc * b = &top;
1641 if( tr_bencDictFindDict( &top, ARGUMENTS, &b )
1642 && tr_bencDictFindDict( b, "torrent-added", &b )
1643 && tr_bencDictFindInt( b, "id", &i ) )
1644 tr_snprintf( id, sizeof(id), "%"PRId64, i );
1645 /* fall-through to default: to give success or failure msg */
1647 default:
1648 if( !tr_bencDictFindStr( &top, "result", &str ) )
1649 status |= EXIT_FAILURE;
1650 else {
1651 printf( "%s:%d responded: \"%s\"\n", host, port, str );
1652 if( strcmp( str, "success") )
1653 status |= EXIT_FAILURE;
1657 tr_bencFree( &top );
1660 return status;
1663 static CURL*
1664 tr_curl_easy_init( struct evbuffer * writebuf )
1666 CURL * curl = curl_easy_init( );
1667 curl_easy_setopt( curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING );
1668 curl_easy_setopt( curl, CURLOPT_WRITEFUNCTION, writeFunc );
1669 curl_easy_setopt( curl, CURLOPT_WRITEDATA, writebuf );
1670 curl_easy_setopt( curl, CURLOPT_HEADERFUNCTION, parseResponseHeader );
1671 curl_easy_setopt( curl, CURLOPT_POST, 1 );
1672 curl_easy_setopt( curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL );
1673 curl_easy_setopt( curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY );
1674 curl_easy_setopt( curl, CURLOPT_VERBOSE, debug );
1675 curl_easy_setopt( curl, CURLOPT_ENCODING, "" ); /* "" tells curl to fill in the blanks with what it was compiled to support */
1676 if( netrc )
1677 curl_easy_setopt( curl, CURLOPT_NETRC_FILE, netrc );
1678 if( auth )
1679 curl_easy_setopt( curl, CURLOPT_USERPWD, auth );
1680 if( sessionId ) {
1681 char * h = tr_strdup_printf( "%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId );
1682 struct curl_slist * custom_headers = curl_slist_append( NULL, h );
1683 curl_easy_setopt( curl, CURLOPT_HTTPHEADER, custom_headers );
1684 /* fixme: leaks */
1686 return curl;
1689 static int
1690 flush( const char * host, int port, tr_benc ** benc )
1692 CURLcode res;
1693 CURL * curl;
1694 int status = EXIT_SUCCESS;
1695 struct evbuffer * buf = evbuffer_new( );
1696 char * json = tr_bencToStr( *benc, TR_FMT_JSON_LEAN, NULL );
1697 char * url = tr_strdup_printf( "http://%s:%d/transmission/rpc", host, port );
1699 curl = tr_curl_easy_init( buf );
1700 curl_easy_setopt( curl, CURLOPT_URL, url );
1701 curl_easy_setopt( curl, CURLOPT_POSTFIELDS, json );
1702 curl_easy_setopt( curl, CURLOPT_TIMEOUT, getTimeoutSecs( json ) );
1704 if( debug )
1705 fprintf( stderr, "posting:\n--------\n%s\n--------\n", json );
1707 if(( res = curl_easy_perform( curl )))
1709 tr_nerr( MY_NAME, "(%s) %s", url, curl_easy_strerror( res ) );
1710 status |= EXIT_FAILURE;
1712 else
1714 long response;
1715 curl_easy_getinfo( curl, CURLINFO_RESPONSE_CODE, &response );
1716 switch( response ) {
1717 case 200:
1718 status |= processResponse( host, port, EVBUFFER_DATA(buf), EVBUFFER_LENGTH(buf) );
1719 break;
1720 case 409:
1721 /* session id failed. our curl header func has already
1722 * pulled the new session id from this response's headers,
1723 * build a new CURL* and try again */
1724 curl_easy_cleanup( curl );
1725 curl = NULL;
1726 flush( host, port, benc );
1727 benc = NULL;
1728 break;
1729 default:
1730 fprintf( stderr, "Unexpected response: %s\n", (char*)EVBUFFER_DATA(buf) );
1731 status |= EXIT_FAILURE;
1732 break;
1736 /* cleanup */
1737 tr_free( url );
1738 tr_free( json );
1739 evbuffer_free( buf );
1740 if( curl != 0 )
1741 curl_easy_cleanup( curl );
1742 if( benc != NULL ) {
1743 tr_bencFree( *benc );
1744 *benc = 0;
1746 return status;
1749 static tr_benc*
1750 ensure_sset( tr_benc ** sset )
1752 tr_benc * args;
1754 if( *sset )
1755 args = tr_bencDictFind( *sset, ARGUMENTS );
1756 else {
1757 *sset = tr_new0( tr_benc, 1 );
1758 tr_bencInitDict( *sset, 3 );
1759 tr_bencDictAddStr( *sset, "method", "session-set" );
1760 args = tr_bencDictAddDict( *sset, ARGUMENTS, 0 );
1763 return args;
1766 static tr_benc*
1767 ensure_tset( tr_benc ** tset )
1769 tr_benc * args;
1771 if( *tset )
1772 args = tr_bencDictFind( *tset, ARGUMENTS );
1773 else {
1774 *tset = tr_new0( tr_benc, 1 );
1775 tr_bencInitDict( *tset, 3 );
1776 tr_bencDictAddStr( *tset, "method", "torrent-set" );
1777 args = tr_bencDictAddDict( *tset, ARGUMENTS, 1 );
1780 return args;
1783 static int
1784 processArgs( const char * host, int port, int argc, const char ** argv )
1786 int c;
1787 int status = EXIT_SUCCESS;
1788 const char * optarg;
1789 tr_benc *sset = 0;
1790 tr_benc *tset = 0;
1791 tr_benc *tadd = 0;
1793 *id = '\0';
1795 while(( c = tr_getopt( getUsage( ), argc, argv, opts, &optarg )))
1797 const int stepMode = getOptMode( c );
1799 if( !stepMode ) /* meta commands */
1801 switch( c )
1803 case 'a': /* add torrent */
1804 if( sset != 0 ) status |= flush( host, port, &sset );
1805 if( tadd != 0 ) status |= flush( host, port, &tadd );
1806 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
1807 tadd = tr_new0( tr_benc, 1 );
1808 tr_bencInitDict( tadd, 3 );
1809 tr_bencDictAddStr( tadd, "method", "torrent-add" );
1810 tr_bencDictAddInt( tadd, "tag", TAG_TORRENT_ADD );
1811 tr_bencDictAddDict( tadd, ARGUMENTS, 0 );
1812 break;
1814 case 'b': /* debug */
1815 debug = TRUE;
1816 break;
1818 case 'n': /* auth */
1819 auth = tr_strdup( optarg );
1820 break;
1822 case 'N': /* netrc */
1823 netrc = tr_strdup( optarg );
1824 break;
1826 case 't': /* set current torrent */
1827 if( tadd != 0 ) status |= flush( host, port, &tadd );
1828 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
1829 tr_strlcpy( id, optarg, sizeof( id ) );
1830 break;
1832 case 'V': /* show version number */
1833 fprintf( stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING );
1834 exit( 0 );
1835 break;
1837 case TR_OPT_ERR:
1838 fprintf( stderr, "invalid option\n" );
1839 showUsage( );
1840 status |= EXIT_FAILURE;
1841 break;
1843 case TR_OPT_UNK:
1844 if( tadd ) {
1845 tr_benc * args = tr_bencDictFind( tadd, ARGUMENTS );
1846 char * tmp = getEncodedMetainfo( optarg );
1847 if( tmp )
1848 tr_bencDictAddStr( args, "metainfo", tmp );
1849 else
1850 tr_bencDictAddStr( args, "filename", optarg );
1851 tr_free( tmp );
1852 } else {
1853 fprintf( stderr, "Unknown option: %s\n", optarg );
1854 status |= EXIT_FAILURE;
1856 break;
1859 else if( stepMode == MODE_TORRENT_GET )
1861 size_t i, n;
1862 tr_benc * top = tr_new0( tr_benc, 1 );
1863 tr_benc * args;
1864 tr_benc * fields;
1865 tr_bencInitDict( top, 3 );
1866 tr_bencDictAddStr( top, "method", "torrent-get" );
1867 args = tr_bencDictAddDict( top, ARGUMENTS, 0 );
1868 fields = tr_bencDictAddList( args, "fields", 0 );
1870 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
1872 switch( c )
1874 case 'i': tr_bencDictAddInt( top, "tag", TAG_DETAILS );
1875 n = TR_N_ELEMENTS( details_keys );
1876 for( i=0; i<n; ++i ) tr_bencListAddStr( fields, details_keys[i] );
1877 addIdArg( args, id );
1878 break;
1879 case 'l': tr_bencDictAddInt( top, "tag", TAG_LIST );
1880 n = TR_N_ELEMENTS( list_keys );
1881 for( i=0; i<n; ++i ) tr_bencListAddStr( fields, list_keys[i] );
1882 break;
1883 case 940: tr_bencDictAddInt( top, "tag", TAG_FILES );
1884 n = TR_N_ELEMENTS( files_keys );
1885 for( i=0; i<n; ++i ) tr_bencListAddStr( fields, files_keys[i] );
1886 addIdArg( args, id );
1887 break;
1888 case 941: tr_bencDictAddInt( top, "tag", TAG_PEERS );
1889 tr_bencListAddStr( fields, "peers" );
1890 addIdArg( args, id );
1891 break;
1892 case 942: tr_bencDictAddInt( top, "tag", TAG_PIECES );
1893 tr_bencListAddStr( fields, "pieces" );
1894 tr_bencListAddStr( fields, "pieceCount" );
1895 addIdArg( args, id );
1896 break;
1897 case 943: tr_bencDictAddInt( top, "tag", TAG_TRACKERS );
1898 tr_bencListAddStr( fields, "trackerStats" );
1899 addIdArg( args, id );
1900 break;
1901 default: assert( "unhandled value" && 0 );
1904 status |= flush( host, port, &top );
1906 else if( stepMode == MODE_SESSION_SET )
1908 tr_benc * args = ensure_sset( &sset );
1910 switch( c )
1912 case 800: tr_bencDictAddStr( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_FILENAME, optarg );
1913 tr_bencDictAddBool( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, TRUE );
1914 break;
1915 case 801: tr_bencDictAddBool( args, TR_PREFS_KEY_SCRIPT_TORRENT_DONE_ENABLED, FALSE );
1916 break;
1917 case 970: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, TRUE );
1918 break;
1919 case 971: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_ENABLED, FALSE );
1920 break;
1921 case 972: tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_DOWN_KBps, numarg( optarg ) );
1922 break;
1923 case 973: tr_bencDictAddInt( args, TR_PREFS_KEY_ALT_SPEED_UP_KBps, numarg( optarg ) );
1924 break;
1925 case 974: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, TRUE );
1926 break;
1927 case 975: tr_bencDictAddBool( args, TR_PREFS_KEY_ALT_SPEED_TIME_ENABLED, FALSE );
1928 break;
1929 case 976: addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_BEGIN, optarg );
1930 break;
1931 case 977: addTime( args, TR_PREFS_KEY_ALT_SPEED_TIME_END, optarg );
1932 break;
1933 case 978: addDays( args, TR_PREFS_KEY_ALT_SPEED_TIME_DAY, optarg );
1934 break;
1935 case 'c': tr_bencDictAddStr( args, TR_PREFS_KEY_INCOMPLETE_DIR, optarg );
1936 tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, TRUE );
1937 break;
1938 case 'C': tr_bencDictAddBool( args, TR_PREFS_KEY_INCOMPLETE_DIR_ENABLED, FALSE );
1939 break;
1940 case 'e': tr_bencDictAddInt( args, TR_PREFS_KEY_MAX_CACHE_SIZE_MB, atoi(optarg) );
1941 break;
1942 case 910: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "required" );
1943 break;
1944 case 911: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "preferred" );
1945 break;
1946 case 912: tr_bencDictAddStr( args, TR_PREFS_KEY_ENCRYPTION, "tolerated" );
1947 break;
1948 case 'm': tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, TRUE );
1949 break;
1950 case 'M': tr_bencDictAddBool( args, TR_PREFS_KEY_PORT_FORWARDING, FALSE );
1951 break;
1952 case 'o': tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, TRUE );
1953 break;
1954 case 'O': tr_bencDictAddBool( args, TR_PREFS_KEY_DHT_ENABLED, FALSE );
1955 break;
1956 case 'p': tr_bencDictAddInt( args, TR_PREFS_KEY_PEER_PORT, numarg( optarg ) );
1957 break;
1958 case 'P': tr_bencDictAddBool( args, TR_PREFS_KEY_PEER_PORT_RANDOM_ON_START, TRUE);
1959 break;
1960 case 'x': tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, TRUE );
1961 break;
1962 case 'X': tr_bencDictAddBool( args, TR_PREFS_KEY_PEX_ENABLED, FALSE );
1963 break;
1964 case 'y': tr_bencDictAddBool( args, TR_PREFS_KEY_LPD_ENABLED, TRUE );
1965 break;
1966 case 'Y': tr_bencDictAddBool( args, TR_PREFS_KEY_LPD_ENABLED, FALSE );
1967 break;
1968 case 953: tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
1969 tr_bencDictAddBool( args, "seedRatioLimited", TRUE );
1970 break;
1971 case 954: tr_bencDictAddBool( args, "seedRatioLimited", FALSE );
1972 break;
1973 case 990: tr_bencDictAddBool( args, TR_PREFS_KEY_START, FALSE );
1974 break;
1975 case 991: tr_bencDictAddBool( args, TR_PREFS_KEY_START, TRUE );
1976 break;
1977 case 992: tr_bencDictAddBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, TRUE );
1978 break;
1979 case 993: tr_bencDictAddBool( args, TR_PREFS_KEY_TRASH_ORIGINAL, FALSE );
1980 break;
1981 default: assert( "unhandled value" && 0 );
1982 break;
1985 else if( stepMode == ( MODE_SESSION_SET | MODE_TORRENT_SET ) )
1987 tr_benc * targs = 0;
1988 tr_benc * sargs = 0;
1990 if( *id )
1991 targs = ensure_tset( &tset );
1992 else
1993 sargs = ensure_sset( &sset );
1995 switch( c )
1997 case 'd': if( targs ) {
1998 tr_bencDictAddInt( targs, "downloadLimit", numarg( optarg ) );
1999 tr_bencDictAddBool( targs, "downloadLimited", TRUE );
2000 } else {
2001 tr_bencDictAddInt( sargs, TR_PREFS_KEY_DSPEED_KBps, numarg( optarg ) );
2002 tr_bencDictAddBool( sargs, TR_PREFS_KEY_DSPEED_ENABLED, TRUE );
2004 break;
2005 case 'D': if( targs )
2006 tr_bencDictAddBool( targs, "downloadLimited", FALSE );
2007 else
2008 tr_bencDictAddBool( sargs, TR_PREFS_KEY_DSPEED_ENABLED, FALSE );
2009 break;
2010 case 'u': if( targs ) {
2011 tr_bencDictAddInt( targs, "uploadLimit", numarg( optarg ) );
2012 tr_bencDictAddBool( targs, "uploadLimited", TRUE );
2013 } else {
2014 tr_bencDictAddInt( sargs, TR_PREFS_KEY_USPEED_KBps, numarg( optarg ) );
2015 tr_bencDictAddBool( sargs, TR_PREFS_KEY_USPEED_ENABLED, TRUE );
2017 break;
2018 case 'U': if( targs )
2019 tr_bencDictAddBool( targs, "uploadLimited", FALSE );
2020 else
2021 tr_bencDictAddBool( sargs, TR_PREFS_KEY_USPEED_ENABLED, FALSE );
2022 break;
2023 case 930: if( targs )
2024 tr_bencDictAddInt( targs, "peer-limit", atoi(optarg) );
2025 else
2026 tr_bencDictAddInt( sargs, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, atoi(optarg) );
2027 break;
2028 default: assert( "unhandled value" && 0 );
2029 break;
2032 else if( stepMode == MODE_TORRENT_SET )
2034 tr_benc * args = ensure_tset( &tset );
2036 switch( c )
2038 case 712: tr_bencListAddInt( tr_bencDictAddList( args, "trackerRemove", 1 ), atoi( optarg ) );
2039 break;
2040 case 950: tr_bencDictAddReal( args, "seedRatioLimit", atof(optarg) );
2041 tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_SINGLE );
2042 break;
2043 case 951: tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_GLOBAL );
2044 break;
2045 case 952: tr_bencDictAddInt( args, "seedRatioMode", TR_RATIOLIMIT_UNLIMITED );
2046 break;
2047 case 984: tr_bencDictAddBool( args, "honorsSessionLimits", TRUE );
2048 break;
2049 case 985: tr_bencDictAddBool( args, "honorsSessionLimits", FALSE );
2050 break;
2051 default: assert( "unhandled value" && 0 );
2052 break;
2055 else if( stepMode == ( MODE_TORRENT_SET | MODE_TORRENT_ADD ) )
2057 tr_benc * args;
2059 if( tadd )
2060 args = tr_bencDictFind( tadd, ARGUMENTS );
2061 else
2062 args = ensure_tset( &tset );
2064 switch( c )
2066 case 'g': addFiles( args, "files-wanted", optarg );
2067 break;
2068 case 'G': addFiles( args, "files-unwanted", optarg );
2069 break;
2070 case 900: addFiles( args, "priority-high", optarg );
2071 break;
2072 case 901: addFiles( args, "priority-normal", optarg );
2073 break;
2074 case 902: addFiles( args, "priority-low", optarg );
2075 break;
2076 case 700: tr_bencDictAddInt( args, "bandwidthPriority", 1 );
2077 break;
2078 case 701: tr_bencDictAddInt( args, "bandwidthPriority", 0 );
2079 break;
2080 case 702: tr_bencDictAddInt( args, "bandwidthPriority", -1 );
2081 break;
2082 case 710: tr_bencListAddStr( tr_bencDictAddList( args, "trackerAdd", 1 ), optarg );
2083 break;
2084 default: assert( "unhandled value" && 0 );
2085 break;
2088 else if( c == 961 ) /* set location */
2090 if( tadd )
2092 tr_benc * args = tr_bencDictFind( tadd, ARGUMENTS );
2093 tr_bencDictAddStr( args, "download-dir", optarg );
2095 else
2097 tr_benc * args;
2098 tr_benc * top = tr_new0( tr_benc, 1 );
2099 tr_bencInitDict( top, 2 );
2100 tr_bencDictAddStr( top, "method", "torrent-set-location" );
2101 args = tr_bencDictAddDict( top, ARGUMENTS, 3 );
2102 tr_bencDictAddStr( args, "location", optarg );
2103 tr_bencDictAddBool( args, "move", FALSE );
2104 addIdArg( args, id );
2105 status |= flush( host, port, &top );
2106 break;
2109 else switch( c )
2111 case 920: /* session-info */
2113 tr_benc * top = tr_new0( tr_benc, 1 );
2114 tr_bencInitDict( top, 2 );
2115 tr_bencDictAddStr( top, "method", "session-get" );
2116 tr_bencDictAddInt( top, "tag", TAG_SESSION );
2117 status |= flush( host, port, &top );
2118 break;
2120 case 's': /* start */
2122 if( tadd )
2123 tr_bencDictAddBool( tr_bencDictFind( tadd, "arguments" ), "paused", FALSE );
2124 else {
2125 tr_benc * top = tr_new0( tr_benc, 1 );
2126 tr_bencInitDict( top, 2 );
2127 tr_bencDictAddStr( top, "method", "torrent-start" );
2128 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
2129 status |= flush( host, port, &top );
2131 break;
2133 case 'S': /* stop */
2135 if( tadd )
2136 tr_bencDictAddBool( tr_bencDictFind( tadd, "arguments" ), "paused", TRUE );
2137 else {
2138 tr_benc * top = tr_new0( tr_benc, 1 );
2139 tr_bencInitDict( top, 2 );
2140 tr_bencDictAddStr( top, "method", "torrent-stop" );
2141 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
2142 status |= flush( host, port, &top );
2144 break;
2146 case 'w':
2148 char * path = absolutify( optarg );
2149 if( tadd )
2150 tr_bencDictAddStr( tr_bencDictFind( tadd, "arguments" ), "download-dir", path );
2151 else {
2152 tr_benc * args = ensure_sset( &sset );
2153 tr_bencDictAddStr( args, "download-dir", path );
2155 tr_free( path );
2156 break;
2158 case 963:
2160 tr_benc * top = tr_new0( tr_benc, 1 );
2161 tr_bencInitDict( top, 1 );
2162 tr_bencDictAddStr( top, "method", "blocklist-update" );
2163 status |= flush( host, port, &top );
2164 break;
2166 case 921:
2168 tr_benc * top = tr_new0( tr_benc, 1 );
2169 tr_bencInitDict( top, 2 );
2170 tr_bencDictAddStr( top, "method", "session-stats" );
2171 tr_bencDictAddInt( top, "tag", TAG_STATS );
2172 status |= flush( host, port, &top );
2173 break;
2175 case 962:
2177 tr_benc * top = tr_new0( tr_benc, 1 );
2178 tr_bencInitDict( top, 2 );
2179 tr_bencDictAddStr( top, "method", "port-test" );
2180 tr_bencDictAddInt( top, "tag", TAG_PORTTEST );
2181 status |= flush( host, port, &top );
2182 break;
2184 case 'v':
2186 tr_benc * top;
2187 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
2188 top = tr_new0( tr_benc, 1 );
2189 tr_bencInitDict( top, 2 );
2190 tr_bencDictAddStr( top, "method", "torrent-verify" );
2191 addIdArg( tr_bencDictAddDict( top, ARGUMENTS, 1 ), id );
2192 status |= flush( host, port, &top );
2193 break;
2195 case 'r':
2196 case 'R':
2198 tr_benc * args;
2199 tr_benc * top = tr_new0( tr_benc, 1 );
2200 tr_bencInitDict( top, 2 );
2201 tr_bencDictAddStr( top, "method", "torrent-remove" );
2202 args = tr_bencDictAddDict( top, ARGUMENTS, 2 );
2203 tr_bencDictAddBool( args, "delete-local-data", c=='R' );
2204 addIdArg( args, id );
2205 status |= flush( host, port, &top );
2206 break;
2208 case 960:
2210 tr_benc * args;
2211 tr_benc * top = tr_new0( tr_benc, 1 );
2212 tr_bencInitDict( top, 2 );
2213 tr_bencDictAddStr( top, "method", "torrent-set-location" );
2214 args = tr_bencDictAddDict( top, ARGUMENTS, 3 );
2215 tr_bencDictAddStr( args, "location", optarg );
2216 tr_bencDictAddBool( args, "move", TRUE );
2217 addIdArg( args, id );
2218 status |= flush( host, port, &top );
2219 break;
2221 default:
2223 fprintf( stderr, "got opt [%d]\n", (int)c );
2224 showUsage( );
2225 break;
2230 if( tadd != 0 ) status |= flush( host, port, &tadd );
2231 if( tset != 0 ) { addIdArg( tr_bencDictFind( tset, ARGUMENTS ), id ); status |= flush( host, port, &tset ); }
2232 if( sset != 0 ) status |= flush( host, port, &sset );
2233 return status;
2236 /* [host:port] or [host] or [port] */
2237 static void
2238 getHostAndPort( int * argc, char ** argv, char ** host, int * port )
2240 if( *argv[1] != '-' )
2242 int i;
2243 const char * s = argv[1];
2244 const char * delim = strchr( s, ':' );
2245 if( delim ) /* user passed in both host and port */
2247 *host = tr_strndup( s, delim - s );
2248 *port = atoi( delim + 1 );
2250 else
2252 char * end;
2253 const int i = strtol( s, &end, 10 );
2254 if( !*end ) /* user passed in a port */
2255 *port = i;
2256 else /* user passed in a host */
2257 *host = tr_strdup( s );
2260 *argc -= 1;
2261 for( i = 1; i < *argc; ++i )
2262 argv[i] = argv[i + 1];
2267 main( int argc, char ** argv )
2269 int port = DEFAULT_PORT;
2270 char * host = NULL;
2271 int exit_status = EXIT_SUCCESS;
2273 if( argc < 2 ) {
2274 showUsage( );
2275 return EXIT_FAILURE;
2278 tr_formatter_mem_init( MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR );
2279 tr_formatter_size_init( DISK_K,DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR );
2280 tr_formatter_speed_init( SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR );
2282 getHostAndPort( &argc, argv, &host, &port );
2283 if( host == NULL )
2284 host = tr_strdup( DEFAULT_HOST );
2286 exit_status = processArgs( host, port, argc, (const char**)argv );
2288 tr_free( host );
2289 return exit_status;