1 /*
2 * This file Copyright (C) 2008-2014 Mnemosyne LLC
3 *
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
6 *
7 */
8
9 #include <assert.h>
10 #include <ctype.h> /* isspace */
11 #include <math.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h> /* strcmp */
15
16 #include <event2/buffer.h>
17 #include <event2/util.h>
18
19 #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
20 #include <curl/curl.h>
21
22 #include <libtransmission/transmission.h>
23 #include <libtransmission/crypto-utils.h>
24 #include <libtransmission/error.h>
25 #include <libtransmission/file.h>
26 #include <libtransmission/log.h>
27 #include <libtransmission/rpcimpl.h>
28 #include <libtransmission/tr-getopt.h>
29 #include <libtransmission/utils.h>
30 #include <libtransmission/variant.h>
31 #include <libtransmission/version.h>
32
33 #define MY_NAME "transmission-remote"
34 #define DEFAULT_HOST "localhost"
35 #define DEFAULT_PORT atoi(TR_DEFAULT_RPC_PORT_STR)
36 #define DEFAULT_URL TR_DEFAULT_RPC_URL_STR "rpc/"
37
38 #define ARGUMENTS TR_KEY_arguments
39
40 #define MEM_K 1024
41 #define MEM_B_STR "B"
42 #define MEM_K_STR "KiB"
43 #define MEM_M_STR "MiB"
44 #define MEM_G_STR "GiB"
45 #define MEM_T_STR "TiB"
46
47 #define DISK_K 1000
48 #define DISK_B_STR "B"
49 #define DISK_K_STR "kB"
50 #define DISK_M_STR "MB"
51 #define DISK_G_STR "GB"
52 #define DISK_T_STR "TB"
53
54 #define SPEED_K 1000
55 #define SPEED_B_STR "B/s"
56 #define SPEED_K_STR "kB/s"
57 #define SPEED_M_STR "MB/s"
58 #define SPEED_G_STR "GB/s"
59 #define SPEED_T_STR "TB/s"
60
61 /***
62 ****
63 **** Display Utilities
64 ****
65 ***/
66
etaToString(char * buf,size_t buflen,int64_t eta)67 static void etaToString(char* buf, size_t buflen, int64_t eta)
68 {
69 if (eta < 0)
70 {
71 tr_snprintf(buf, buflen, "Unknown");
72 }
73 else if (eta < 60)
74 {
75 tr_snprintf(buf, buflen, "%" PRId64 " sec", eta);
76 }
77 else if (eta < (60 * 60))
78 {
79 tr_snprintf(buf, buflen, "%" PRId64 " min", eta / 60);
80 }
81 else if (eta < (60 * 60 * 24))
82 {
83 tr_snprintf(buf, buflen, "%" PRId64 " hrs", eta / (60 * 60));
84 }
85 else
86 {
87 tr_snprintf(buf, buflen, "%" PRId64 " days", eta / (60 * 60 * 24));
88 }
89 }
90
tr_strltime(char * buf,int seconds,size_t buflen)91 static char* tr_strltime(char* buf, int seconds, size_t buflen)
92 {
93 int days;
94 int hours;
95 int minutes;
96 int total_seconds;
97 char b[128];
98 char d[128];
99 char h[128];
100 char m[128];
101 char s[128];
102 char t[128];
103
104 if (seconds < 0)
105 {
106 seconds = 0;
107 }
108
109 total_seconds = seconds;
110 days = seconds / 86400;
111 hours = (seconds % 86400) / 3600;
112 minutes = (seconds % 3600) / 60;
113 seconds = (seconds % 3600) % 60;
114
115 tr_snprintf(d, sizeof(d), "%d %s", days, days == 1 ? "day" : "days");
116 tr_snprintf(h, sizeof(h), "%d %s", hours, hours == 1 ? "hour" : "hours");
117 tr_snprintf(m, sizeof(m), "%d %s", minutes, minutes == 1 ? "minute" : "minutes");
118 tr_snprintf(s, sizeof(s), "%d %s", seconds, seconds == 1 ? "second" : "seconds");
119 tr_snprintf(t, sizeof(t), "%d %s", total_seconds, total_seconds == 1 ? "second" : "seconds");
120
121 if (days != 0)
122 {
123 if (days >= 4 || hours == 0)
124 {
125 tr_strlcpy(b, d, sizeof(b));
126 }
127 else
128 {
129 tr_snprintf(b, sizeof(b), "%s, %s", d, h);
130 }
131 }
132 else if (hours != 0)
133 {
134 if (hours >= 4 || minutes == 0)
135 {
136 tr_strlcpy(b, h, sizeof(b));
137 }
138 else
139 {
140 tr_snprintf(b, sizeof(b), "%s, %s", h, m);
141 }
142 }
143 else if (minutes != 0)
144 {
145 if (minutes >= 4 || seconds == 0)
146 {
147 tr_strlcpy(b, m, sizeof(b));
148 }
149 else
150 {
151 tr_snprintf(b, sizeof(b), "%s, %s", m, s);
152 }
153 }
154 else
155 {
156 tr_strlcpy(b, s, sizeof(b));
157 }
158
159 tr_snprintf(buf, buflen, "%s (%s)", b, t);
160 return buf;
161 }
162
strlpercent(char * buf,double x,size_t buflen)163 static char* strlpercent(char* buf, double x, size_t buflen)
164 {
165 return tr_strpercent(buf, x, buflen);
166 }
167
strlratio2(char * buf,double ratio,size_t buflen)168 static char* strlratio2(char* buf, double ratio, size_t buflen)
169 {
170 return tr_strratio(buf, buflen, ratio, "Inf");
171 }
172
strlratio(char * buf,int64_t numerator,int64_t denominator,size_t buflen)173 static char* strlratio(char* buf, int64_t numerator, int64_t denominator, size_t buflen)
174 {
175 double ratio;
176
177 if (denominator != 0)
178 {
179 ratio = numerator / (double)denominator;
180 }
181 else if (numerator != 0)
182 {
183 ratio = TR_RATIO_INF;
184 }
185 else
186 {
187 ratio = TR_RATIO_NA;
188 }
189
190 return strlratio2(buf, ratio, buflen);
191 }
192
strlmem(char * buf,int64_t bytes,size_t buflen)193 static char* strlmem(char* buf, int64_t bytes, size_t buflen)
194 {
195 if (bytes == 0)
196 {
197 tr_strlcpy(buf, "None", buflen);
198 }
199 else
200 {
201 tr_formatter_mem_B(buf, bytes, buflen);
202 }
203
204 return buf;
205 }
206
strlsize(char * buf,int64_t bytes,size_t buflen)207 static char* strlsize(char* buf, int64_t bytes, size_t buflen)
208 {
209 if (bytes < 0)
210 {
211 tr_strlcpy(buf, "Unknown", buflen);
212 }
213 else if (bytes == 0)
214 {
215 tr_strlcpy(buf, "None", buflen);
216 }
217 else
218 {
219 tr_formatter_size_B(buf, bytes, buflen);
220 }
221
222 return buf;
223 }
224
225 enum
226 {
227 TAG_SESSION,
228 TAG_STATS,
229 TAG_DETAILS,
230 TAG_FILES,
231 TAG_LIST,
232 TAG_PEERS,
233 TAG_PIECES,
234 TAG_PORTTEST,
235 TAG_TORRENT_ADD,
236 TAG_TRACKERS
237 };
238
getUsage(void)239 static char const* getUsage(void)
240 {
241 return MY_NAME " " LONG_VERSION_STRING "\n"
242 "A fast and easy BitTorrent client\n"
243 "https://transmissionbt.com/\n"
244 "\n"
245 "Usage: " MY_NAME " [host] [options]\n"
246 " " MY_NAME " [port] [options]\n"
247 " " MY_NAME " [host:port] [options]\n"
248 " " MY_NAME " [http(s?)://host:port/transmission/] [options]\n"
249 "\n"
250 "See the man page for detailed explanations and many examples.";
251 }
252
253 /***
254 ****
255 **** Command-Line Arguments
256 ****
257 ***/
258
259 static tr_option opts[] =
260 {
261 { 'a', "add", "Add torrent files by filename or URL", "a", false, NULL },
262 { 970, "alt-speed", "Use the alternate Limits", "as", false, NULL },
263 { 971, "no-alt-speed", "Don't use the alternate Limits", "AS", false, NULL },
264 { 972, "alt-speed-downlimit", "max alternate download speed (in "SPEED_K_STR ")", "asd", true, "<speed>" },
265 { 973, "alt-speed-uplimit", "max alternate upload speed (in "SPEED_K_STR ")", "asu", true, "<speed>" },
266 { 974, "alt-speed-scheduler", "Use the scheduled on/off times", "asc", false, NULL },
267 { 975, "no-alt-speed-scheduler", "Don't use the scheduled on/off times", "ASC", false, NULL },
268 { 976, "alt-speed-time-begin", "Time to start using the alt speed limits (in hhmm)", NULL, true, "<time>" },
269 { 977, "alt-speed-time-end", "Time to stop using the alt speed limits (in hhmm)", NULL, true, "<time>" },
270 { 978, "alt-speed-days", "Numbers for any/all days of the week - eg. \"1-7\"", NULL, true, "<days>" },
271 { 963, "blocklist-update", "Blocklist update", NULL, false, NULL },
272 { 'c', "incomplete-dir", "Where to store new torrents until they're complete", "c", true, "<dir>" },
273 { 'C', "no-incomplete-dir", "Don't store incomplete torrents in a different location", "C", false, NULL },
274 { 'b', "debug", "Print debugging information", "b", false, NULL },
275 { 'd', "downlimit", "Set the max download speed in "SPEED_K_STR " for the current torrent(s) or globally", "d", true,
276 "<speed>" },
277 { 'D', "no-downlimit", "Disable max download speed for the current torrent(s) or globally", "D", false, NULL },
278 { 'e', "cache", "Set the maximum size of the session's memory cache (in " MEM_M_STR ")", "e", true, "<size>" },
279 { 910, "encryption-required", "Encrypt all peer connections", "er", false, NULL },
280 { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", false, NULL },
281 { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", false, NULL },
282 { 850, "exit", "Tell the transmission session to shut down", NULL, false, NULL },
283 { 940, "files", "List the current torrent(s)' files", "f", false, NULL },
284 { 'g', "get", "Mark files for download", "g", true, "<files>" },
285 { 'G', "no-get", "Mark files for not downloading", "G", true, "<files>" },
286 { 'i', "info", "Show the current torrent(s)' details", "i", false, NULL },
287 { 940, "info-files", "List the current torrent(s)' files", "if", false, NULL },
288 { 941, "info-peers", "List the current torrent(s)' peers", "ip", false, NULL },
289 { 942, "info-pieces", "List the current torrent(s)' pieces", "ic", false, NULL },
290 { 943, "info-trackers", "List the current torrent(s)' trackers", "it", false, NULL },
291 { 920, "session-info", "Show the session's details", "si", false, NULL },
292 { 921, "session-stats", "Show the session's statistics", "st", false, NULL },
293 { 'l', "list", "List all torrents", "l", false, NULL },
294 { 'L', "labels", "Set the current torrents' labels", "L", true, "<label[,label...]>" },
295 { 960, "move", "Move current torrent's data to a new folder", NULL, true, "<path>" },
296 { 961, "find", "Tell Transmission where to find a torrent's data", NULL, true, "<path>" },
297 { 'm', "portmap", "Enable portmapping via NAT-PMP or UPnP", "m", false, NULL },
298 { 'M', "no-portmap", "Disable portmapping", "M", false, NULL },
299 { 'n', "auth", "Set username and password", "n", true, "<user:pw>" },
300 { 810, "authenv", "Set authentication info from the TR_AUTH environment variable (user:pw)", "ne", false, NULL },
301 { 'N', "netrc", "Set authentication info from a .netrc file", "N", true, "<file>" },
302 { 820, "ssl", "Use SSL when talking to daemon", NULL, false, NULL },
303 { 'o', "dht", "Enable distributed hash tables (DHT)", "o", false, NULL },
304 { 'O', "no-dht", "Disable distributed hash tables (DHT)", "O", false, NULL },
305 { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", true, "<port>" },
306 { 962, "port-test", "Port testing", "pt", false, NULL },
307 { 'P', "random-port", "Random port for incoming peers", "P", false, NULL },
308 { 900, "priority-high", "Try to download these file(s) first", "ph", true, "<files>" },
309 { 901, "priority-normal", "Try to download these file(s) normally", "pn", true, "<files>" },
310 { 902, "priority-low", "Try to download these file(s) last", "pl", true, "<files>" },
311 { 700, "bandwidth-high", "Give this torrent first chance at available bandwidth", "Bh", false, NULL },
312 { 701, "bandwidth-normal", "Give this torrent bandwidth left over by high priority torrents", "Bn", false, NULL },
313 { 702, "bandwidth-low", "Give this torrent bandwidth left over by high and normal priority torrents", "Bl", false, NULL },
314 { 600, "reannounce", "Reannounce the current torrent(s)", NULL, false, NULL },
315 { 'r', "remove", "Remove the current torrent(s)", "r", false, NULL },
316 { 930, "peers", "Set the maximum number of peers for the current torrent(s) or globally", "pr", true, "<max>" },
317 { 840, "remove-and-delete", "Remove the current torrent(s) and delete local data", "rad", false, NULL },
318 { 800, "torrent-done-script", "Specify a script to run when a torrent finishes", NULL, true, "<file>" },
319 { 801, "no-torrent-done-script", "Don't run a script when torrents finish", NULL, false, NULL },
320 { 950, "seedratio", "Let the current torrent(s) seed until a specific ratio", "sr", true, "ratio" },
321 { 951, "seedratio-default", "Let the current torrent(s) use the global seedratio settings", "srd", false, NULL },
322 { 952, "no-seedratio", "Let the current torrent(s) seed regardless of ratio", "SR", false, NULL },
323 { 953, "global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed until a specific ratio",
324 "gsr", true, "ratio" },
325 { 954, "no-global-seedratio", "All torrents, unless overridden by a per-torrent setting, should seed regardless of ratio",
326 "GSR", false, NULL },
327 { 710, "tracker-add", "Add a tracker to a torrent", "td", true, "<tracker>" },
328 { 712, "tracker-remove", "Remove a tracker from a torrent", "tr", true, "<trackerId>" },
329 { 's', "start", "Start the current torrent(s)", "s", false, NULL },
330 { 'S', "stop", "Stop the current torrent(s)", "S", false, NULL },
331 { 't', "torrent", "Set the current torrent(s)", "t", true, "<torrent>" },
332 { 990, "start-paused", "Start added torrents paused", NULL, false, NULL },
333 { 991, "no-start-paused", "Start added torrents unpaused", NULL, false, NULL },
334 { 992, "trash-torrent", "Delete torrents after adding", NULL, false, NULL },
335 { 993, "no-trash-torrent", "Do not delete torrents after adding", NULL, false, NULL },
336 { 984, "honor-session", "Make the current torrent(s) honor the session limits", "hl", false, NULL },
337 { 985, "no-honor-session", "Make the current torrent(s) not honor the session limits", "HL", false, NULL },
338 { 'u', "uplimit", "Set the max upload speed in "SPEED_K_STR " for the current torrent(s) or globally", "u", true,
339 "<speed>" },
340 { 'U', "no-uplimit", "Disable max upload speed for the current torrent(s) or globally", "U", false, NULL },
341 { 830, "utp", "Enable uTP for peer connections", NULL, false, NULL },
342 { 831, "no-utp", "Disable uTP for peer connections", NULL, false, NULL },
343 { 'v', "verify", "Verify the current torrent(s)", "v", false, NULL },
344 { 'V', "version", "Show version number and exit", "V", false, NULL },
345 { 'w', "download-dir", "When used in conjunction with --add, set the new torrent's download folder. "
346 "Otherwise, set the default download folder", "w", true, "<path>" },
347 { 'x', "pex", "Enable peer exchange (PEX)", "x", false, NULL },
348 { 'X', "no-pex", "Disable peer exchange (PEX)", "X", false, NULL },
349 { 'y', "lpd", "Enable local peer discovery (LPD)", "y", false, NULL },
350 { 'Y', "no-lpd", "Disable local peer discovery (LPD)", "Y", false, NULL },
351 { 941, "peer-info", "List the current torrent(s)' peers", "pi", false, NULL },
352 { 0, NULL, NULL, NULL, false, NULL }
353 };
354
showUsage(void)355 static void showUsage(void)
356 {
357 tr_getopt_usage(MY_NAME, getUsage(), opts);
358 }
359
numarg(char const * arg)360 static int numarg(char const* arg)
361 {
362 char* end = NULL;
363 long const num = strtol(arg, &end, 10);
364
365 if (*end != '\0')
366 {
367 fprintf(stderr, "Not a number: \"%s\"\n", arg);
368 showUsage();
369 exit(EXIT_FAILURE);
370 }
371
372 return num;
373 }
374
375 enum
376 {
377 MODE_TORRENT_START = (1 << 0),
378 MODE_TORRENT_STOP = (1 << 1),
379 MODE_TORRENT_VERIFY = (1 << 2),
380 MODE_TORRENT_REANNOUNCE = (1 << 3),
381 MODE_TORRENT_SET = (1 << 4),
382 MODE_TORRENT_GET = (1 << 5),
383 MODE_TORRENT_ADD = (1 << 6),
384 MODE_TORRENT_REMOVE = (1 << 7),
385 MODE_TORRENT_SET_LOCATION = (1 << 8),
386 MODE_SESSION_SET = (1 << 9),
387 MODE_SESSION_GET = (1 << 10),
388 MODE_SESSION_STATS = (1 << 11),
389 MODE_SESSION_CLOSE = (1 << 12),
390 MODE_BLOCKLIST_UPDATE = (1 << 13),
391 MODE_PORT_TEST = (1 << 14)
392 };
393
getOptMode(int val)394 static int getOptMode(int val)
395 {
396 switch (val)
397 {
398 case TR_OPT_ERR:
399 case TR_OPT_UNK:
400 case 'a': /* add torrent */
401 case 'b': /* debug */
402 case 'n': /* auth */
403 case 810: /* authenv */
404 case 'N': /* netrc */
405 case 820: /* UseSSL */
406 case 't': /* set current torrent */
407 case 'V': /* show version number */
408 return 0;
409
410 case 'c': /* incomplete-dir */
411 case 'C': /* no-incomplete-dir */
412 case 'e': /* cache */
413 case 'm': /* portmap */
414 case 'M': /* "no-portmap */
415 case 'o': /* dht */
416 case 'O': /* no-dht */
417 case 'p': /* incoming peer port */
418 case 'P': /* random incoming peer port */
419 case 'x': /* pex */
420 case 'X': /* no-pex */
421 case 'y': /* lpd */
422 case 'Y': /* no-lpd */
423 case 800: /* torrent-done-script */
424 case 801: /* no-torrent-done-script */
425 case 830: /* utp */
426 case 831: /* no-utp */
427 case 970: /* alt-speed */
428 case 971: /* no-alt-speed */
429 case 972: /* alt-speed-downlimit */
430 case 973: /* alt-speed-uplimit */
431 case 974: /* alt-speed-scheduler */
432 case 975: /* no-alt-speed-scheduler */
433 case 976: /* alt-speed-time-begin */
434 case 977: /* alt-speed-time-end */
435 case 978: /* alt-speed-days */
436 case 910: /* encryption-required */
437 case 911: /* encryption-preferred */
438 case 912: /* encryption-tolerated */
439 case 953: /* global-seedratio */
440 case 954: /* no-global-seedratio */
441 case 990: /* start-paused */
442 case 991: /* no-start-paused */
443 case 992: /* trash-torrent */
444 case 993: /* no-trash-torrent */
445 return MODE_SESSION_SET;
446
447 case 'L': /* labels */
448 case 712: /* tracker-remove */
449 case 950: /* seedratio */
450 case 951: /* seedratio-default */
451 case 952: /* no-seedratio */
452 case 984: /* honor-session */
453 case 985: /* no-honor-session */
454 return MODE_TORRENT_SET;
455
456 case 920: /* session-info */
457 return MODE_SESSION_GET;
458
459 case 'g': /* get */
460 case 'G': /* no-get */
461 case 700: /* torrent priority-high */
462 case 701: /* torrent priority-normal */
463 case 702: /* torrent priority-low */
464 case 710: /* tracker-add */
465 case 900: /* file priority-high */
466 case 901: /* file priority-normal */
467 case 902: /* file priority-low */
468 return MODE_TORRENT_SET | MODE_TORRENT_ADD;
469
470 case 961: /* find */
471 return MODE_TORRENT_SET_LOCATION | MODE_TORRENT_ADD;
472
473 case 'i': /* info */
474 case 'l': /* list all torrents */
475 case 940: /* info-files */
476 case 941: /* info-peer */
477 case 942: /* info-pieces */
478 case 943: /* info-tracker */
479 return MODE_TORRENT_GET;
480
481 case 'd': /* download speed limit */
482 case 'D': /* no download speed limit */
483 case 'u': /* upload speed limit */
484 case 'U': /* no upload speed limit */
485 case 930: /* peers */
486 return MODE_SESSION_SET | MODE_TORRENT_SET;
487
488 case 's': /* start */
489 return MODE_TORRENT_START | MODE_TORRENT_ADD;
490
491 case 'S': /* stop */
492 return MODE_TORRENT_STOP | MODE_TORRENT_ADD;
493
494 case 'w': /* download-dir */
495 return MODE_SESSION_SET | MODE_TORRENT_ADD;
496
497 case 850: /* session-close */
498 return MODE_SESSION_CLOSE;
499
500 case 963: /* blocklist-update */
501 return MODE_BLOCKLIST_UPDATE;
502
503 case 921: /* session-stats */
504 return MODE_SESSION_STATS;
505
506 case 'v': /* verify */
507 return MODE_TORRENT_VERIFY;
508
509 case 600: /* reannounce */
510 return MODE_TORRENT_REANNOUNCE;
511
512 case 962: /* port-test */
513 return MODE_PORT_TEST;
514
515 case 'r': /* remove */
516 case 840: /* remove and delete */
517 return MODE_TORRENT_REMOVE;
518
519 case 960: /* move */
520 return MODE_TORRENT_SET_LOCATION;
521
522 default:
523 fprintf(stderr, "unrecognized argument %d\n", val);
524 assert("unrecognized argument" && 0);
525 return 0;
526 }
527 }
528
529 static bool debug = false;
530 static char* auth = NULL;
531 static char* netrc = NULL;
532 static char* sessionId = NULL;
533 static bool UseSSL = false;
534
getEncodedMetainfo(char const * filename)535 static char* getEncodedMetainfo(char const* filename)
536 {
537 size_t len = 0;
538 char* b64 = NULL;
539 uint8_t* buf = tr_loadFile(filename, &len, NULL);
540
541 if (buf != NULL)
542 {
543 b64 = tr_base64_encode(buf, len, NULL);
544 tr_free(buf);
545 }
546
547 return b64;
548 }
549
addIdArg(tr_variant * args,char const * id,char const * fallback)550 static void addIdArg(tr_variant* args, char const* id, char const* fallback)
551 {
552 if (tr_str_is_empty(id))
553 {
554 id = fallback;
555
556 if (tr_str_is_empty(id))
557 {
558 fprintf(stderr, "No torrent specified! Please use the -t option first.\n");
559 id = "-1"; /* no torrent will have this ID, so will act as a no-op */
560 }
561 }
562
563 if (tr_strcmp0(id, "active") == 0)
564 {
565 tr_variantDictAddStr(args, TR_KEY_ids, "recently-active");
566 }
567 else if (strcmp(id, "all") != 0)
568 {
569 bool isList = strchr(id, ',') != NULL || strchr(id, '-') != NULL;
570 bool isNum = true;
571
572 for (char const* pch = id; isNum && *pch != '\0'; ++pch)
573 {
574 isNum = isdigit(*pch);
575 }
576
577 if (isNum || isList)
578 {
579 tr_rpc_parse_list_str(tr_variantDictAdd(args, TR_KEY_ids), id, strlen(id));
580 }
581 else
582 {
583 tr_variantDictAddStr(args, TR_KEY_ids, id); /* it's a torrent sha hash */
584 }
585 }
586 }
587
addTime(tr_variant * args,tr_quark const key,char const * arg)588 static void addTime(tr_variant* args, tr_quark const key, char const* arg)
589 {
590 int time;
591 bool success = false;
592
593 if (arg != NULL && strlen(arg) == 4)
594 {
595 char const hh[3] = { arg[0], arg[1], '\0' };
596 char const mm[3] = { arg[2], arg[3], '\0' };
597 int const hour = atoi(hh);
598 int const min = atoi(mm);
599
600 if (0 <= hour && hour < 24 && 0 <= min && min < 60)
601 {
602 time = min + (hour * 60);
603 success = true;
604 }
605 }
606
607 if (success)
608 {
609 tr_variantDictAddInt(args, key, time);
610 }
611 else
612 {
613 fprintf(stderr, "Please specify the time of day in 'hhmm' format.\n");
614 }
615 }
616
addDays(tr_variant * args,tr_quark const key,char const * arg)617 static void addDays(tr_variant* args, tr_quark const key, char const* arg)
618 {
619 int days = 0;
620
621 if (arg != NULL)
622 {
623 int valueCount;
624 int* values;
625
626 values = tr_parseNumberRange(arg, TR_BAD_SIZE, &valueCount);
627
628 for (int i = 0; i < valueCount; ++i)
629 {
630 if (values[i] < 0 || values[i] > 7)
631 {
632 continue;
633 }
634
635 if (values[i] == 7)
636 {
637 values[i] = 0;
638 }
639
640 days |= 1 << values[i];
641 }
642
643 tr_free(values);
644 }
645
646 if (days != 0)
647 {
648 tr_variantDictAddInt(args, key, days);
649 }
650 else
651 {
652 fprintf(stderr, "Please specify the days of the week in '1-3,4,7' format.\n");
653 }
654 }
655
addLabels(tr_variant * args,char const * arg)656 static void addLabels(tr_variant* args, char const* arg)
657 {
658 tr_variant* labels;
659 if (!tr_variantDictFindList(args, TR_KEY_labels, &labels))
660 {
661 labels = tr_variantDictAddList(args, TR_KEY_labels, 10);
662 }
663
664 char* argcpy = tr_strdup(arg);
665 char* const tmp = argcpy; /* save copied string start pointer to free later */
666 char* token;
667 while ((token = tr_strsep(&argcpy, ",")) != NULL)
668 {
669 tr_strstrip(token);
670 if (!tr_str_is_empty(token))
671 {
672 tr_variantListAddStr(labels, token);
673 }
674 }
675
676 tr_free(tmp);
677 }
678
addFiles(tr_variant * args,tr_quark const key,char const * arg)679 static void addFiles(tr_variant* args, tr_quark const key, char const* arg)
680 {
681 tr_variant* files = tr_variantDictAddList(args, key, 100);
682
683 if (tr_str_is_empty(arg))
684 {
685 fprintf(stderr, "No files specified!\n");
686 arg = "-1"; /* no file will have this index, so should be a no-op */
687 }
688
689 if (strcmp(arg, "all") != 0)
690 {
691 int valueCount;
692 int* values = tr_parseNumberRange(arg, TR_BAD_SIZE, &valueCount);
693
694 for (int i = 0; i < valueCount; ++i)
695 {
696 tr_variantListAddInt(files, values[i]);
697 }
698
699 tr_free(values);
700 }
701 }
702
703 static tr_quark const files_keys[] =
704 {
705 TR_KEY_files,
706 TR_KEY_name,
707 TR_KEY_priorities,
708 TR_KEY_wanted
709 };
710
711 static tr_quark const details_keys[] =
712 {
713 TR_KEY_activityDate,
714 TR_KEY_addedDate,
715 TR_KEY_bandwidthPriority,
716 TR_KEY_comment,
717 TR_KEY_corruptEver,
718 TR_KEY_creator,
719 TR_KEY_dateCreated,
720 TR_KEY_desiredAvailable,
721 TR_KEY_doneDate,
722 TR_KEY_downloadDir,
723 TR_KEY_downloadedEver,
724 TR_KEY_downloadLimit,
725 TR_KEY_downloadLimited,
726 TR_KEY_error,
727 TR_KEY_errorString,
728 TR_KEY_eta,
729 TR_KEY_hashString,
730 TR_KEY_haveUnchecked,
731 TR_KEY_haveValid,
732 TR_KEY_honorsSessionLimits,
733 TR_KEY_id,
734 TR_KEY_isFinished,
735 TR_KEY_isPrivate,
736 TR_KEY_labels,
737 TR_KEY_leftUntilDone,
738 TR_KEY_magnetLink,
739 TR_KEY_name,
740 TR_KEY_peersConnected,
741 TR_KEY_peersGettingFromUs,
742 TR_KEY_peersSendingToUs,
743 TR_KEY_peer_limit,
744 TR_KEY_pieceCount,
745 TR_KEY_pieceSize,
746 TR_KEY_rateDownload,
747 TR_KEY_rateUpload,
748 TR_KEY_recheckProgress,
749 TR_KEY_secondsDownloading,
750 TR_KEY_secondsSeeding,
751 TR_KEY_seedRatioMode,
752 TR_KEY_seedRatioLimit,
753 TR_KEY_sizeWhenDone,
754 TR_KEY_startDate,
755 TR_KEY_status,
756 TR_KEY_totalSize,
757 TR_KEY_uploadedEver,
758 TR_KEY_uploadLimit,
759 TR_KEY_uploadLimited,
760 TR_KEY_webseeds,
761 TR_KEY_webseedsSendingToUs
762 };
763
764 static tr_quark const list_keys[] =
765 {
766 TR_KEY_error,
767 TR_KEY_errorString,
768 TR_KEY_eta,
769 TR_KEY_id,
770 TR_KEY_isFinished,
771 TR_KEY_leftUntilDone,
772 TR_KEY_name,
773 TR_KEY_peersGettingFromUs,
774 TR_KEY_peersSendingToUs,
775 TR_KEY_rateDownload,
776 TR_KEY_rateUpload,
777 TR_KEY_sizeWhenDone,
778 TR_KEY_status,
779 TR_KEY_uploadRatio
780 };
781
writeFunc(void * ptr,size_t size,size_t nmemb,void * buf)782 static size_t writeFunc(void* ptr, size_t size, size_t nmemb, void* buf)
783 {
784 size_t const byteCount = size * nmemb;
785 evbuffer_add(buf, ptr, byteCount);
786 return byteCount;
787 }
788
789 /* look for a session id in the header in case the server gives back a 409 */
parseResponseHeader(void * ptr,size_t size,size_t nmemb,void * stream UNUSED)790 static size_t parseResponseHeader(void* ptr, size_t size, size_t nmemb, void* stream UNUSED)
791 {
792 char const* line = ptr;
793 size_t const line_len = size * nmemb;
794 char const* key = TR_RPC_SESSION_ID_HEADER ": ";
795 size_t const key_len = strlen(key);
796
797 if (line_len >= key_len && evutil_ascii_strncasecmp(line, key, key_len) == 0)
798 {
799 char const* begin = line + key_len;
800 char const* end = begin;
801
802 while (!isspace(*end))
803 {
804 ++end;
805 }
806
807 tr_free(sessionId);
808 sessionId = tr_strndup(begin, end - begin);
809 }
810
811 return line_len;
812 }
813
getTimeoutSecs(char const * req)814 static long getTimeoutSecs(char const* req)
815 {
816 if (strstr(req, "\"method\":\"blocklist-update\"") != NULL)
817 {
818 return 300L;
819 }
820
821 return 60L; /* default value */
822 }
823
getStatusString(tr_variant * t,char * buf,size_t buflen)824 static char* getStatusString(tr_variant* t, char* buf, size_t buflen)
825 {
826 int64_t status;
827 bool boolVal;
828
829 if (!tr_variantDictFindInt(t, TR_KEY_status, &status))
830 {
831 *buf = '\0';
832 }
833 else
834 {
835 switch (status)
836 {
837 case TR_STATUS_DOWNLOAD_WAIT:
838 case TR_STATUS_SEED_WAIT:
839 tr_strlcpy(buf, "Queued", buflen);
840 break;
841
842 case TR_STATUS_STOPPED:
843 if (tr_variantDictFindBool(t, TR_KEY_isFinished, &boolVal) && boolVal)
844 {
845 tr_strlcpy(buf, "Finished", buflen);
846 }
847 else
848 {
849 tr_strlcpy(buf, "Stopped", buflen);
850 }
851
852 break;
853
854 case TR_STATUS_CHECK_WAIT:
855 case TR_STATUS_CHECK:
856 {
857 char const* str = status == TR_STATUS_CHECK_WAIT ? "Will Verify" : "Verifying";
858 double percent;
859
860 if (tr_variantDictFindReal(t, TR_KEY_recheckProgress, &percent))
861 {
862 tr_snprintf(buf, buflen, "%s (%.0f%%)", str, (floor)(percent * 100.0));
863 }
864 else
865 {
866 tr_strlcpy(buf, str, buflen);
867 }
868
869 break;
870 }
871
872 case TR_STATUS_DOWNLOAD:
873 case TR_STATUS_SEED:
874 {
875 int64_t fromUs = 0;
876 int64_t toUs = 0;
877 tr_variantDictFindInt(t, TR_KEY_peersGettingFromUs, &fromUs);
878 tr_variantDictFindInt(t, TR_KEY_peersSendingToUs, &toUs);
879
880 if (fromUs != 0 && toUs != 0)
881 {
882 tr_strlcpy(buf, "Up & Down", buflen);
883 }
884 else if (toUs != 0)
885 {
886 tr_strlcpy(buf, "Downloading", buflen);
887 }
888 else if (fromUs != 0)
889 {
890 int64_t leftUntilDone = 0;
891 tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &leftUntilDone);
892
893 if (leftUntilDone > 0)
894 {
895 tr_strlcpy(buf, "Uploading", buflen);
896 }
897 else
898 {
899 tr_strlcpy(buf, "Seeding", buflen);
900 }
901 }
902 else
903 {
904 tr_strlcpy(buf, "Idle", buflen);
905 }
906
907 break;
908 }
909
910 default:
911 tr_strlcpy(buf, "Unknown", buflen);
912 break;
913 }
914 }
915
916 return buf;
917 }
918
919 static char const* bandwidthPriorityNames[] =
920 {
921 "Low",
922 "Normal",
923 "High",
924 "Invalid"
925 };
926
printDetails(tr_variant * top)927 static void printDetails(tr_variant* top)
928 {
929 tr_variant* args;
930 tr_variant* torrents;
931
932 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
933 {
934 for (int ti = 0, tCount = tr_variantListSize(torrents); ti < tCount; ++ti)
935 {
936 tr_variant* t = tr_variantListChild(torrents, ti);
937 tr_variant* l;
938 char const* str;
939 char buf[512];
940 char buf2[512];
941 int64_t i;
942 int64_t j;
943 int64_t k;
944 bool boolVal;
945 double d;
946
947 printf("NAME\n");
948
949 if (tr_variantDictFindInt(t, TR_KEY_id, &i))
950 {
951 printf(" Id: %" PRId64 "\n", i);
952 }
953
954 if (tr_variantDictFindStr(t, TR_KEY_name, &str, NULL))
955 {
956 printf(" Name: %s\n", str);
957 }
958
959 if (tr_variantDictFindStr(t, TR_KEY_hashString, &str, NULL))
960 {
961 printf(" Hash: %s\n", str);
962 }
963
964 if (tr_variantDictFindStr(t, TR_KEY_magnetLink, &str, NULL))
965 {
966 printf(" Magnet: %s\n", str);
967 }
968
969 if (tr_variantDictFindList(t, TR_KEY_labels, &l))
970 {
971 int const n = tr_variantListSize(l);
972 char const* str;
973 printf(" Labels: ");
974 for (int i = 0; i < n; i++)
975 {
976 if (tr_variantGetStr(tr_variantListChild(l, i), &str, NULL))
977 {
978 printf(i == 0 ? "%s" : ", %s", str);
979 }
980 }
981
982 printf("\n");
983 }
984
985 printf("\n");
986
987 printf("TRANSFER\n");
988 getStatusString(t, buf, sizeof(buf));
989 printf(" State: %s\n", buf);
990
991 if (tr_variantDictFindStr(t, TR_KEY_downloadDir, &str, NULL))
992 {
993 printf(" Location: %s\n", str);
994 }
995
996 if (tr_variantDictFindInt(t, TR_KEY_sizeWhenDone, &i) && tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &j))
997 {
998 strlpercent(buf, 100.0 * (i - j) / i, sizeof(buf));
999 printf(" Percent Done: %s%%\n", buf);
1000 }
1001
1002 if (tr_variantDictFindInt(t, TR_KEY_eta, &i))
1003 {
1004 printf(" ETA: %s\n", tr_strltime(buf, i, sizeof(buf)));
1005 }
1006
1007 if (tr_variantDictFindInt(t, TR_KEY_rateDownload, &i))
1008 {
1009 printf(" Download Speed: %s\n", tr_formatter_speed_KBps(buf, i / (double)tr_speed_K, sizeof(buf)));
1010 }
1011
1012 if (tr_variantDictFindInt(t, TR_KEY_rateUpload, &i))
1013 {
1014 printf(" Upload Speed: %s\n", tr_formatter_speed_KBps(buf, i / (double)tr_speed_K, sizeof(buf)));
1015 }
1016
1017 if (tr_variantDictFindInt(t, TR_KEY_haveUnchecked, &i) && tr_variantDictFindInt(t, TR_KEY_haveValid, &j))
1018 {
1019 strlsize(buf, i + j, sizeof(buf));
1020 strlsize(buf2, j, sizeof(buf2));
1021 printf(" Have: %s (%s verified)\n", buf, buf2);
1022 }
1023
1024 if (tr_variantDictFindInt(t, TR_KEY_sizeWhenDone, &i))
1025 {
1026 if (i < 1)
1027 {
1028 printf(" Availability: None\n");
1029 }
1030
1031 if (tr_variantDictFindInt(t, TR_KEY_desiredAvailable, &j) && tr_variantDictFindInt(t, TR_KEY_leftUntilDone, &k))
1032 {
1033 j += i - k;
1034 strlpercent(buf, 100.0 * j / i, sizeof(buf));
1035 printf(" Availability: %s%%\n", buf);
1036 }
1037
1038 if (tr_variantDictFindInt(t, TR_KEY_totalSize, &j))
1039 {
1040 strlsize(buf2, i, sizeof(buf2));
1041 strlsize(buf, j, sizeof(buf));
1042 printf(" Total size: %s (%s wanted)\n", buf, buf2);
1043 }
1044 }
1045
1046 if (tr_variantDictFindInt(t, TR_KEY_downloadedEver, &i) && tr_variantDictFindInt(t, TR_KEY_uploadedEver, &j))
1047 {
1048 strlsize(buf, i, sizeof(buf));
1049 printf(" Downloaded: %s\n", buf);
1050 strlsize(buf, j, sizeof(buf));
1051 printf(" Uploaded: %s\n", buf);
1052 strlratio(buf, j, i, sizeof(buf));
1053 printf(" Ratio: %s\n", buf);
1054 }
1055
1056 if (tr_variantDictFindInt(t, TR_KEY_corruptEver, &i))
1057 {
1058 strlsize(buf, i, sizeof(buf));
1059 printf(" Corrupt DL: %s\n", buf);
1060 }
1061
1062 if (tr_variantDictFindStr(t, TR_KEY_errorString, &str, NULL) && !tr_str_is_empty(str) &&
1063 tr_variantDictFindInt(t, TR_KEY_error, &i) && i != 0)
1064 {
1065 switch (i)
1066 {
1067 case TR_STAT_TRACKER_WARNING:
1068 printf(" Tracker gave a warning: %s\n", str);
1069 break;
1070
1071 case TR_STAT_TRACKER_ERROR:
1072 printf(" Tracker gave an error: %s\n", str);
1073 break;
1074
1075 case TR_STAT_LOCAL_ERROR:
1076 printf(" Error: %s\n", str);
1077 break;
1078
1079 default:
1080 break; /* no error */
1081 }
1082 }
1083
1084 if (tr_variantDictFindInt(t, TR_KEY_peersConnected, &i) &&
1085 tr_variantDictFindInt(t, TR_KEY_peersGettingFromUs, &j) &&
1086 tr_variantDictFindInt(t, TR_KEY_peersSendingToUs, &k))
1087 {
1088 printf(" Peers: connected to %" PRId64 ", uploading to %" PRId64 ", downloading from %" PRId64 "\n", i, j, k);
1089 }
1090
1091 if (tr_variantDictFindList(t, TR_KEY_webseeds, &l) && tr_variantDictFindInt(t, TR_KEY_webseedsSendingToUs, &i))
1092 {
1093 int64_t const n = tr_variantListSize(l);
1094
1095 if (n > 0)
1096 {
1097 printf(" Web Seeds: downloading from %" PRId64 " of %" PRId64 " web seeds\n", i, n);
1098 }
1099 }
1100
1101 printf("\n");
1102
1103 printf("HISTORY\n");
1104
1105 if (tr_variantDictFindInt(t, TR_KEY_addedDate, &i) && i != 0)
1106 {
1107 time_t const tt = i;
1108 printf(" Date added: %s", ctime(&tt));
1109 }
1110
1111 if (tr_variantDictFindInt(t, TR_KEY_doneDate, &i) && i != 0)
1112 {
1113 time_t const tt = i;
1114 printf(" Date finished: %s", ctime(&tt));
1115 }
1116
1117 if (tr_variantDictFindInt(t, TR_KEY_startDate, &i) && i != 0)
1118 {
1119 time_t const tt = i;
1120 printf(" Date started: %s", ctime(&tt));
1121 }
1122
1123 if (tr_variantDictFindInt(t, TR_KEY_activityDate, &i) && i != 0)
1124 {
1125 time_t const tt = i;
1126 printf(" Latest activity: %s", ctime(&tt));
1127 }
1128
1129 if (tr_variantDictFindInt(t, TR_KEY_secondsDownloading, &i) && i > 0)
1130 {
1131 printf(" Downloading Time: %s\n", tr_strltime(buf, i, sizeof(buf)));
1132 }
1133
1134 if (tr_variantDictFindInt(t, TR_KEY_secondsSeeding, &i) && i > 0)
1135 {
1136 printf(" Seeding Time: %s\n", tr_strltime(buf, i, sizeof(buf)));
1137 }
1138
1139 printf("\n");
1140
1141 printf("ORIGINS\n");
1142
1143 if (tr_variantDictFindInt(t, TR_KEY_dateCreated, &i) && i != 0)
1144 {
1145 time_t const tt = i;
1146 printf(" Date created: %s", ctime(&tt));
1147 }
1148
1149 if (tr_variantDictFindBool(t, TR_KEY_isPrivate, &boolVal))
1150 {
1151 printf(" Public torrent: %s\n", (boolVal ? "No" : "Yes"));
1152 }
1153
1154 if (tr_variantDictFindStr(t, TR_KEY_comment, &str, NULL) && !tr_str_is_empty(str))
1155 {
1156 printf(" Comment: %s\n", str);
1157 }
1158
1159 if (tr_variantDictFindStr(t, TR_KEY_creator, &str, NULL) && !tr_str_is_empty(str))
1160 {
1161 printf(" Creator: %s\n", str);
1162 }
1163
1164 if (tr_variantDictFindInt(t, TR_KEY_pieceCount, &i))
1165 {
1166 printf(" Piece Count: %" PRId64 "\n", i);
1167 }
1168
1169 if (tr_variantDictFindInt(t, TR_KEY_pieceSize, &i))
1170 {
1171 printf(" Piece Size: %s\n", strlmem(buf, i, sizeof(buf)));
1172 }
1173
1174 printf("\n");
1175
1176 printf("LIMITS & BANDWIDTH\n");
1177
1178 if (tr_variantDictFindBool(t, TR_KEY_downloadLimited, &boolVal) &&
1179 tr_variantDictFindInt(t, TR_KEY_downloadLimit, &i))
1180 {
1181 printf(" Download Limit: ");
1182
1183 if (boolVal)
1184 {
1185 printf("%s\n", tr_formatter_speed_KBps(buf, i, sizeof(buf)));
1186 }
1187 else
1188 {
1189 printf("Unlimited\n");
1190 }
1191 }
1192
1193 if (tr_variantDictFindBool(t, TR_KEY_uploadLimited, &boolVal) && tr_variantDictFindInt(t, TR_KEY_uploadLimit, &i))
1194 {
1195 printf(" Upload Limit: ");
1196
1197 if (boolVal)
1198 {
1199 printf("%s\n", tr_formatter_speed_KBps(buf, i, sizeof(buf)));
1200 }
1201 else
1202 {
1203 printf("Unlimited\n");
1204 }
1205 }
1206
1207 if (tr_variantDictFindInt(t, TR_KEY_seedRatioMode, &i))
1208 {
1209 switch (i)
1210 {
1211 case TR_RATIOLIMIT_GLOBAL:
1212 printf(" Ratio Limit: Default\n");
1213 break;
1214
1215 case TR_RATIOLIMIT_SINGLE:
1216 if (tr_variantDictFindReal(t, TR_KEY_seedRatioLimit, &d))
1217 {
1218 printf(" Ratio Limit: %s\n", strlratio2(buf, d, sizeof(buf)));
1219 }
1220
1221 break;
1222
1223 case TR_RATIOLIMIT_UNLIMITED:
1224 printf(" Ratio Limit: Unlimited\n");
1225 break;
1226
1227 default:
1228 break;
1229 }
1230 }
1231
1232 if (tr_variantDictFindBool(t, TR_KEY_honorsSessionLimits, &boolVal))
1233 {
1234 printf(" Honors Session Limits: %s\n", (boolVal ? "Yes" : "No"));
1235 }
1236
1237 if (tr_variantDictFindInt(t, TR_KEY_peer_limit, &i))
1238 {
1239 printf(" Peer limit: %" PRId64 "\n", i);
1240 }
1241
1242 if (tr_variantDictFindInt(t, TR_KEY_bandwidthPriority, &i))
1243 {
1244 printf(" Bandwidth Priority: %s\n", bandwidthPriorityNames[(i + 1) & 3]);
1245 }
1246
1247 printf("\n");
1248 }
1249 }
1250 }
1251
printFileList(tr_variant * top)1252 static void printFileList(tr_variant* top)
1253 {
1254 tr_variant* args;
1255 tr_variant* torrents;
1256
1257 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1258 {
1259 for (int i = 0, in = tr_variantListSize(torrents); i < in; ++i)
1260 {
1261 tr_variant* d = tr_variantListChild(torrents, i);
1262 tr_variant* files;
1263 tr_variant* priorities;
1264 tr_variant* wanteds;
1265 char const* name;
1266
1267 if (tr_variantDictFindStr(d, TR_KEY_name, &name, NULL) && tr_variantDictFindList(d, TR_KEY_files, &files) &&
1268 tr_variantDictFindList(d, TR_KEY_priorities, &priorities) && tr_variantDictFindList(d, TR_KEY_wanted, &wanteds))
1269 {
1270 int const jn = tr_variantListSize(files);
1271 printf("%s (%d files):\n", name, jn);
1272 printf("%3s %4s %8s %3s %9s %s\n", "#", "Done", "Priority", "Get", "Size", "Name");
1273
1274 for (int j = 0; j < jn; ++j)
1275 {
1276 int64_t have;
1277 int64_t length;
1278 int64_t priority;
1279 bool wanted;
1280 char const* filename;
1281 tr_variant* file = tr_variantListChild(files, j);
1282
1283 if (tr_variantDictFindInt(file, TR_KEY_length, &length) &&
1284 tr_variantDictFindStr(file, TR_KEY_name, &filename, NULL) &&
1285 tr_variantDictFindInt(file, TR_KEY_bytesCompleted, &have) &&
1286 tr_variantGetInt(tr_variantListChild(priorities, j), &priority) &&
1287 tr_variantGetBool(tr_variantListChild(wanteds, j), &wanted))
1288 {
1289 char sizestr[64];
1290 double percent = (double)have / length;
1291 char const* pristr;
1292 strlsize(sizestr, length, sizeof(sizestr));
1293
1294 switch (priority)
1295 {
1296 case TR_PRI_LOW:
1297 pristr = "Low";
1298 break;
1299
1300 case TR_PRI_HIGH:
1301 pristr = "High";
1302 break;
1303
1304 default:
1305 pristr = "Normal";
1306 break;
1307 }
1308
1309 printf("%3d: %3.0f%% %-8s %-3s %9s %s\n", j, (floor)(100.0 * percent), pristr, wanted ? "Yes" : "No",
1310 sizestr, filename);
1311 }
1312 }
1313 }
1314 }
1315 }
1316 }
1317
printPeersImpl(tr_variant * peers)1318 static void printPeersImpl(tr_variant* peers)
1319 {
1320 printf("%-40s %-12s %-5s %-6s %-6s %s\n", "Address", "Flags", "Done", "Down", "Up", "Client");
1321
1322 for (int i = 0, n = tr_variantListSize(peers); i < n; ++i)
1323 {
1324 double progress;
1325 char const* address;
1326 char const* client;
1327 char const* flagstr;
1328 int64_t rateToClient;
1329 int64_t rateToPeer;
1330 tr_variant* d = tr_variantListChild(peers, i);
1331
1332 if (tr_variantDictFindStr(d, TR_KEY_address, &address, NULL) &&
1333 tr_variantDictFindStr(d, TR_KEY_clientName, &client, NULL) &&
1334 tr_variantDictFindReal(d, TR_KEY_progress, &progress) &&
1335 tr_variantDictFindStr(d, TR_KEY_flagStr, &flagstr, NULL) &&
1336 tr_variantDictFindInt(d, TR_KEY_rateToClient, &rateToClient) &&
1337 tr_variantDictFindInt(d, TR_KEY_rateToPeer, &rateToPeer))
1338 {
1339 printf("%-40s %-12s %-5.1f %6.1f %6.1f %s\n", address, flagstr, (progress * 100.0),
1340 rateToClient / (double)tr_speed_K, rateToPeer / (double)tr_speed_K, client);
1341 }
1342 }
1343 }
1344
printPeers(tr_variant * top)1345 static void printPeers(tr_variant* top)
1346 {
1347 tr_variant* args;
1348 tr_variant* torrents;
1349
1350 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1351 {
1352 for (int i = 0, n = tr_variantListSize(torrents); i < n; ++i)
1353 {
1354 tr_variant* peers;
1355 tr_variant* torrent = tr_variantListChild(torrents, i);
1356
1357 if (tr_variantDictFindList(torrent, TR_KEY_peers, &peers))
1358 {
1359 printPeersImpl(peers);
1360
1361 if (i + 1 < n)
1362 {
1363 printf("\n");
1364 }
1365 }
1366 }
1367 }
1368 }
1369
printPiecesImpl(uint8_t const * raw,size_t rawlen,size_t j)1370 static void printPiecesImpl(uint8_t const* raw, size_t rawlen, size_t j)
1371 {
1372 size_t len;
1373 char* str = tr_base64_decode(raw, rawlen, &len);
1374 printf(" ");
1375
1376 for (size_t i = 0, k = 0; k < len; ++k)
1377 {
1378 for (int e = 0; i < j && e < 8; ++e, ++i)
1379 {
1380 printf("%c", (str[k] & (1 << (7 - e))) != 0 ? '1' : '0');
1381 }
1382
1383 printf(" ");
1384
1385 if (i % 64 == 0)
1386 {
1387 printf("\n ");
1388 }
1389 }
1390
1391 printf("\n");
1392 tr_free(str);
1393 }
1394
printPieces(tr_variant * top)1395 static void printPieces(tr_variant* top)
1396 {
1397 tr_variant* args;
1398 tr_variant* torrents;
1399
1400 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1401 {
1402 for (int i = 0, n = tr_variantListSize(torrents); i < n; ++i)
1403 {
1404 int64_t j;
1405 uint8_t const* raw;
1406 size_t rawlen;
1407 tr_variant* torrent = tr_variantListChild(torrents, i);
1408
1409 if (tr_variantDictFindRaw(torrent, TR_KEY_pieces, &raw, &rawlen) &&
1410 tr_variantDictFindInt(torrent, TR_KEY_pieceCount, &j))
1411 {
1412 assert(j >= 0);
1413 printPiecesImpl(raw, rawlen, (size_t)j);
1414
1415 if (i + 1 < n)
1416 {
1417 printf("\n");
1418 }
1419 }
1420 }
1421 }
1422 }
1423
printPortTest(tr_variant * top)1424 static void printPortTest(tr_variant* top)
1425 {
1426 tr_variant* args;
1427
1428 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args))
1429 {
1430 bool boolVal;
1431
1432 if (tr_variantDictFindBool(args, TR_KEY_port_is_open, &boolVal))
1433 {
1434 printf("Port is open: %s\n", boolVal ? "Yes" : "No");
1435 }
1436 }
1437 }
1438
printTorrentList(tr_variant * top)1439 static void printTorrentList(tr_variant* top)
1440 {
1441 tr_variant* args;
1442 tr_variant* list;
1443
1444 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &list))
1445 {
1446 int64_t total_size = 0;
1447 double total_up = 0;
1448 double total_down = 0;
1449 char haveStr[32];
1450
1451 printf("%6s %-4s %9s %-8s %6s %6s %-5s %-11s %s\n", "ID", "Done", "Have", "ETA", "Up", "Down", "Ratio",
1452 "Status", "Name");
1453
1454 for (int i = 0, n = tr_variantListSize(list); i < n; ++i)
1455 {
1456 int64_t id;
1457 int64_t eta;
1458 int64_t status;
1459 int64_t up;
1460 int64_t down;
1461 int64_t sizeWhenDone;
1462 int64_t leftUntilDone;
1463 double ratio;
1464 char const* name;
1465 tr_variant* d = tr_variantListChild(list, i);
1466
1467 if (tr_variantDictFindInt(d, TR_KEY_eta, &eta) &&
1468 tr_variantDictFindInt(d, TR_KEY_id, &id) &&
1469 tr_variantDictFindInt(d, TR_KEY_leftUntilDone, &leftUntilDone) &&
1470 tr_variantDictFindStr(d, TR_KEY_name, &name, NULL) &&
1471 tr_variantDictFindInt(d, TR_KEY_rateDownload, &down) &&
1472 tr_variantDictFindInt(d, TR_KEY_rateUpload, &up) &&
1473 tr_variantDictFindInt(d, TR_KEY_sizeWhenDone, &sizeWhenDone) &&
1474 tr_variantDictFindInt(d, TR_KEY_status, &status) &&
1475 tr_variantDictFindReal(d, TR_KEY_uploadRatio, &ratio))
1476 {
1477 char etaStr[16];
1478 char statusStr[64];
1479 char ratioStr[32];
1480 char doneStr[8];
1481 int64_t error;
1482 char errorMark;
1483
1484 if (sizeWhenDone != 0)
1485 {
1486 tr_snprintf(doneStr, sizeof(doneStr), "%d%%", (int)(100.0 * (sizeWhenDone - leftUntilDone) / sizeWhenDone));
1487 }
1488 else
1489 {
1490 tr_strlcpy(doneStr, "n/a", sizeof(doneStr));
1491 }
1492
1493 strlsize(haveStr, sizeWhenDone - leftUntilDone, sizeof(haveStr));
1494
1495 if (leftUntilDone != 0 || eta != -1)
1496 {
1497 etaToString(etaStr, sizeof(etaStr), eta);
1498 }
1499 else
1500 {
1501 tr_snprintf(etaStr, sizeof(etaStr), "Done");
1502 }
1503
1504 if (tr_variantDictFindInt(d, TR_KEY_error, &error) && error)
1505 {
1506 errorMark = '*';
1507 }
1508 else
1509 {
1510 errorMark = ' ';
1511 }
1512
1513 printf("%6d%c %4s %9s %-8s %6.1f %6.1f %5s %-11s %s\n", (int)id, errorMark, doneStr, haveStr, etaStr,
1514 up / (double)tr_speed_K, down / (double)tr_speed_K, strlratio2(ratioStr, ratio, sizeof(ratioStr)),
1515 getStatusString(d, statusStr, sizeof(statusStr)), name);
1516
1517 total_up += up;
1518 total_down += down;
1519 total_size += sizeWhenDone - leftUntilDone;
1520 }
1521 }
1522
1523 printf("Sum: %9s %6.1f %6.1f\n", strlsize(haveStr, total_size, sizeof(haveStr)),
1524 total_up / (double)tr_speed_K, total_down / (double)tr_speed_K);
1525 }
1526 }
1527
printTrackersImpl(tr_variant * trackerStats)1528 static void printTrackersImpl(tr_variant* trackerStats)
1529 {
1530 char buf[512];
1531 tr_variant* t;
1532
1533 for (int i = 0; (t = tr_variantListChild(trackerStats, i)) != NULL; ++i)
1534 {
1535 int64_t downloadCount;
1536 bool hasAnnounced;
1537 bool hasScraped;
1538 char const* host;
1539 int64_t id;
1540 bool isBackup;
1541 int64_t lastAnnouncePeerCount;
1542 char const* lastAnnounceResult;
1543 int64_t lastAnnounceStartTime;
1544 bool lastAnnounceSucceeded;
1545 int64_t lastAnnounceTime;
1546 bool lastAnnounceTimedOut;
1547 char const* lastScrapeResult;
1548 bool lastScrapeSucceeded;
1549 int64_t lastScrapeStartTime;
1550 int64_t lastScrapeTime;
1551 bool lastScrapeTimedOut;
1552 int64_t leecherCount;
1553 int64_t nextAnnounceTime;
1554 int64_t nextScrapeTime;
1555 int64_t seederCount;
1556 int64_t tier;
1557 int64_t announceState;
1558 int64_t scrapeState;
1559
1560 if (tr_variantDictFindInt(t, TR_KEY_downloadCount, &downloadCount) &&
1561 tr_variantDictFindBool(t, TR_KEY_hasAnnounced, &hasAnnounced) &&
1562 tr_variantDictFindBool(t, TR_KEY_hasScraped, &hasScraped) &&
1563 tr_variantDictFindStr(t, TR_KEY_host, &host, NULL) &&
1564 tr_variantDictFindInt(t, TR_KEY_id, &id) &&
1565 tr_variantDictFindBool(t, TR_KEY_isBackup, &isBackup) &&
1566 tr_variantDictFindInt(t, TR_KEY_announceState, &announceState) &&
1567 tr_variantDictFindInt(t, TR_KEY_scrapeState, &scrapeState) &&
1568 tr_variantDictFindInt(t, TR_KEY_lastAnnouncePeerCount, &lastAnnouncePeerCount) &&
1569 tr_variantDictFindStr(t, TR_KEY_lastAnnounceResult, &lastAnnounceResult, NULL) &&
1570 tr_variantDictFindInt(t, TR_KEY_lastAnnounceStartTime, &lastAnnounceStartTime) &&
1571 tr_variantDictFindBool(t, TR_KEY_lastAnnounceSucceeded, &lastAnnounceSucceeded) &&
1572 tr_variantDictFindInt(t, TR_KEY_lastAnnounceTime, &lastAnnounceTime) &&
1573 tr_variantDictFindBool(t, TR_KEY_lastAnnounceTimedOut, &lastAnnounceTimedOut) &&
1574 tr_variantDictFindStr(t, TR_KEY_lastScrapeResult, &lastScrapeResult, NULL) &&
1575 tr_variantDictFindInt(t, TR_KEY_lastScrapeStartTime, &lastScrapeStartTime) &&
1576 tr_variantDictFindBool(t, TR_KEY_lastScrapeSucceeded, &lastScrapeSucceeded) &&
1577 tr_variantDictFindInt(t, TR_KEY_lastScrapeTime, &lastScrapeTime) &&
1578 tr_variantDictFindBool(t, TR_KEY_lastScrapeTimedOut, &lastScrapeTimedOut) &&
1579 tr_variantDictFindInt(t, TR_KEY_leecherCount, &leecherCount) &&
1580 tr_variantDictFindInt(t, TR_KEY_nextAnnounceTime, &nextAnnounceTime) &&
1581 tr_variantDictFindInt(t, TR_KEY_nextScrapeTime, &nextScrapeTime) &&
1582 tr_variantDictFindInt(t, TR_KEY_seederCount, &seederCount) &&
1583 tr_variantDictFindInt(t, TR_KEY_tier, &tier))
1584 {
1585 time_t const now = time(NULL);
1586
1587 printf("\n");
1588 printf(" Tracker %d: %s\n", (int)(id), host);
1589
1590 if (isBackup)
1591 {
1592 printf(" Backup on tier %d\n", (int)tier);
1593 }
1594 else
1595 {
1596 printf(" Active in tier %d\n", (int)tier);
1597 }
1598
1599 if (!isBackup)
1600 {
1601 if (hasAnnounced && announceState != TR_TRACKER_INACTIVE)
1602 {
1603 tr_strltime(buf, now - lastAnnounceTime, sizeof(buf));
1604
1605 if (lastAnnounceSucceeded)
1606 {
1607 printf(" Got a list of %d peers %s ago\n", (int)lastAnnouncePeerCount, buf);
1608 }
1609 else if (lastAnnounceTimedOut)
1610 {
1611 printf(" Peer list request timed out; will retry\n");
1612 }
1613 else
1614 {
1615 printf(" Got an error \"%s\" %s ago\n", lastAnnounceResult, buf);
1616 }
1617 }
1618
1619 switch (announceState)
1620 {
1621 case TR_TRACKER_INACTIVE:
1622 printf(" No updates scheduled\n");
1623 break;
1624
1625 case TR_TRACKER_WAITING:
1626 tr_strltime(buf, nextAnnounceTime - now, sizeof(buf));
1627 printf(" Asking for more peers in %s\n", buf);
1628 break;
1629
1630 case TR_TRACKER_QUEUED:
1631 printf(" Queued to ask for more peers\n");
1632 break;
1633
1634 case TR_TRACKER_ACTIVE:
1635 tr_strltime(buf, now - lastAnnounceStartTime, sizeof(buf));
1636 printf(" Asking for more peers now... %s\n", buf);
1637 break;
1638 }
1639
1640 if (hasScraped)
1641 {
1642 tr_strltime(buf, now - lastScrapeTime, sizeof(buf));
1643
1644 if (lastScrapeSucceeded)
1645 {
1646 printf(" Tracker had %d seeders and %d leechers %s ago\n", (int)seederCount, (int)leecherCount, buf);
1647 }
1648 else if (lastScrapeTimedOut)
1649 {
1650 printf(" Tracker scrape timed out; will retry\n");
1651 }
1652 else
1653 {
1654 printf(" Got a scrape error \"%s\" %s ago\n", lastScrapeResult, buf);
1655 }
1656 }
1657
1658 switch (scrapeState)
1659 {
1660 case TR_TRACKER_INACTIVE:
1661 break;
1662
1663 case TR_TRACKER_WAITING:
1664 tr_strltime(buf, nextScrapeTime - now, sizeof(buf));
1665 printf(" Asking for peer counts in %s\n", buf);
1666 break;
1667
1668 case TR_TRACKER_QUEUED:
1669 printf(" Queued to ask for peer counts\n");
1670 break;
1671
1672 case TR_TRACKER_ACTIVE:
1673 tr_strltime(buf, now - lastScrapeStartTime, sizeof(buf));
1674 printf(" Asking for peer counts now... %s\n", buf);
1675 break;
1676 }
1677 }
1678 }
1679 }
1680 }
1681
printTrackers(tr_variant * top)1682 static void printTrackers(tr_variant* top)
1683 {
1684 tr_variant* args;
1685 tr_variant* torrents;
1686
1687 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args) && tr_variantDictFindList(args, TR_KEY_torrents, &torrents))
1688 {
1689 for (int i = 0, n = tr_variantListSize(torrents); i < n; ++i)
1690 {
1691 tr_variant* trackerStats;
1692 tr_variant* torrent = tr_variantListChild(torrents, i);
1693
1694 if (tr_variantDictFindList(torrent, TR_KEY_trackerStats, &trackerStats))
1695 {
1696 printTrackersImpl(trackerStats);
1697
1698 if (i + 1 < n)
1699 {
1700 printf("\n");
1701 }
1702 }
1703 }
1704 }
1705 }
1706
printSession(tr_variant * top)1707 static void printSession(tr_variant* top)
1708 {
1709 tr_variant* args;
1710
1711 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args))
1712 {
1713 int64_t i;
1714 char buf[64];
1715 bool boolVal;
1716 char const* str;
1717
1718 printf("VERSION\n");
1719
1720 if (tr_variantDictFindStr(args, TR_KEY_version, &str, NULL))
1721 {
1722 printf(" Daemon version: %s\n", str);
1723 }
1724
1725 if (tr_variantDictFindInt(args, TR_KEY_rpc_version, &i))
1726 {
1727 printf(" RPC version: %" PRId64 "\n", i);
1728 }
1729
1730 if (tr_variantDictFindInt(args, TR_KEY_rpc_version_minimum, &i))
1731 {
1732 printf(" RPC minimum version: %" PRId64 "\n", i);
1733 }
1734
1735 printf("\n");
1736
1737 printf("CONFIG\n");
1738
1739 if (tr_variantDictFindStr(args, TR_KEY_config_dir, &str, NULL))
1740 {
1741 printf(" Configuration directory: %s\n", str);
1742 }
1743
1744 if (tr_variantDictFindStr(args, TR_KEY_download_dir, &str, NULL))
1745 {
1746 printf(" Download directory: %s\n", str);
1747 }
1748
1749 if (tr_variantDictFindInt(args, TR_KEY_peer_port, &i))
1750 {
1751 printf(" Listenport: %" PRId64 "\n", i);
1752 }
1753
1754 if (tr_variantDictFindBool(args, TR_KEY_port_forwarding_enabled, &boolVal))
1755 {
1756 printf(" Portforwarding enabled: %s\n", boolVal ? "Yes" : "No");
1757 }
1758
1759 if (tr_variantDictFindBool(args, TR_KEY_utp_enabled, &boolVal))
1760 {
1761 printf(" uTP enabled: %s\n", (boolVal ? "Yes" : "No"));
1762 }
1763
1764 if (tr_variantDictFindBool(args, TR_KEY_dht_enabled, &boolVal))
1765 {
1766 printf(" Distributed hash table enabled: %s\n", boolVal ? "Yes" : "No");
1767 }
1768
1769 if (tr_variantDictFindBool(args, TR_KEY_lpd_enabled, &boolVal))
1770 {
1771 printf(" Local peer discovery enabled: %s\n", boolVal ? "Yes" : "No");
1772 }
1773
1774 if (tr_variantDictFindBool(args, TR_KEY_pex_enabled, &boolVal))
1775 {
1776 printf(" Peer exchange allowed: %s\n", boolVal ? "Yes" : "No");
1777 }
1778
1779 if (tr_variantDictFindStr(args, TR_KEY_encryption, &str, NULL))
1780 {
1781 printf(" Encryption: %s\n", str);
1782 }
1783
1784 if (tr_variantDictFindInt(args, TR_KEY_cache_size_mb, &i))
1785 {
1786 printf(" Maximum memory cache size: %s\n", tr_formatter_mem_MB(buf, i, sizeof(buf)));
1787 }
1788
1789 printf("\n");
1790
1791 {
1792 bool altEnabled;
1793 bool altTimeEnabled;
1794 bool upEnabled;
1795 bool downEnabled;
1796 bool seedRatioLimited;
1797 int64_t altDown;
1798 int64_t altUp;
1799 int64_t altBegin;
1800 int64_t altEnd;
1801 int64_t altDay;
1802 int64_t upLimit;
1803 int64_t downLimit;
1804 int64_t peerLimit;
1805 double seedRatioLimit;
1806
1807 if (tr_variantDictFindInt(args, TR_KEY_alt_speed_down, &altDown) &&
1808 tr_variantDictFindBool(args, TR_KEY_alt_speed_enabled, &altEnabled) &&
1809 tr_variantDictFindInt(args, TR_KEY_alt_speed_time_begin, &altBegin) &&
1810 tr_variantDictFindBool(args, TR_KEY_alt_speed_time_enabled, &altTimeEnabled) &&
1811 tr_variantDictFindInt(args, TR_KEY_alt_speed_time_end, &altEnd) &&
1812 tr_variantDictFindInt(args, TR_KEY_alt_speed_time_day, &altDay) &&
1813 tr_variantDictFindInt(args, TR_KEY_alt_speed_up, &altUp) &&
1814 tr_variantDictFindInt(args, TR_KEY_peer_limit_global, &peerLimit) &&
1815 tr_variantDictFindInt(args, TR_KEY_speed_limit_down, &downLimit) &&
1816 tr_variantDictFindBool(args, TR_KEY_speed_limit_down_enabled, &downEnabled) &&
1817 tr_variantDictFindInt(args, TR_KEY_speed_limit_up, &upLimit) &&
1818 tr_variantDictFindBool(args, TR_KEY_speed_limit_up_enabled, &upEnabled) &&
1819 tr_variantDictFindReal(args, TR_KEY_seedRatioLimit, &seedRatioLimit) &&
1820 tr_variantDictFindBool(args, TR_KEY_seedRatioLimited, &seedRatioLimited))
1821 {
1822 char buf[128];
1823 char buf2[128];
1824 char buf3[128];
1825
1826 printf("LIMITS\n");
1827 printf(" Peer limit: %" PRId64 "\n", peerLimit);
1828
1829 if (seedRatioLimited)
1830 {
1831 strlratio2(buf, seedRatioLimit, sizeof(buf));
1832 }
1833 else
1834 {
1835 tr_strlcpy(buf, "Unlimited", sizeof(buf));
1836 }
1837
1838 printf(" Default seed ratio limit: %s\n", buf);
1839
1840 if (altEnabled)
1841 {
1842 tr_formatter_speed_KBps(buf, altUp, sizeof(buf));
1843 }
1844 else if (upEnabled)
1845 {
1846 tr_formatter_speed_KBps(buf, upLimit, sizeof(buf));
1847 }
1848 else
1849 {
1850 tr_strlcpy(buf, "Unlimited", sizeof(buf));
1851 }
1852
1853 printf(" Upload speed limit: %s (%s limit: %s; %s turtle limit: %s)\n", buf,
1854 upEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf2, upLimit, sizeof(buf2)),
1855 altEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf3, altUp, sizeof(buf3)));
1856
1857 if (altEnabled)
1858 {
1859 tr_formatter_speed_KBps(buf, altDown, sizeof(buf));
1860 }
1861 else if (downEnabled)
1862 {
1863 tr_formatter_speed_KBps(buf, downLimit, sizeof(buf));
1864 }
1865 else
1866 {
1867 tr_strlcpy(buf, "Unlimited", sizeof(buf));
1868 }
1869
1870 printf(" Download speed limit: %s (%s limit: %s; %s turtle limit: %s)\n", buf,
1871 downEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf2, downLimit, sizeof(buf2)),
1872 altEnabled ? "Enabled" : "Disabled", tr_formatter_speed_KBps(buf3, altDown, sizeof(buf3)));
1873
1874 if (altTimeEnabled)
1875 {
1876 printf(" Turtle schedule: %02d:%02d - %02d:%02d ", (int)(altBegin / 60), (int)(altBegin % 60),
1877 (int)(altEnd / 60), (int)(altEnd % 60));
1878
1879 if ((altDay & TR_SCHED_SUN) != 0)
1880 {
1881 printf("Sun ");
1882 }
1883
1884 if ((altDay & TR_SCHED_MON) != 0)
1885 {
1886 printf("Mon ");
1887 }
1888
1889 if ((altDay & TR_SCHED_TUES) != 0)
1890 {
1891 printf("Tue ");
1892 }
1893
1894 if ((altDay & TR_SCHED_WED) != 0)
1895 {
1896 printf("Wed ");
1897 }
1898
1899 if ((altDay & TR_SCHED_THURS) != 0)
1900 {
1901 printf("Thu ");
1902 }
1903
1904 if ((altDay & TR_SCHED_FRI) != 0)
1905 {
1906 printf("Fri ");
1907 }
1908
1909 if ((altDay & TR_SCHED_SAT) != 0)
1910 {
1911 printf("Sat ");
1912 }
1913
1914 printf("\n");
1915 }
1916 }
1917 }
1918
1919 printf("\n");
1920
1921 printf("MISC\n");
1922
1923 if (tr_variantDictFindBool(args, TR_KEY_start_added_torrents, &boolVal))
1924 {
1925 printf(" Autostart added torrents: %s\n", boolVal ? "Yes" : "No");
1926 }
1927
1928 if (tr_variantDictFindBool(args, TR_KEY_trash_original_torrent_files, &boolVal))
1929 {
1930 printf(" Delete automatically added torrents: %s\n", boolVal ? "Yes" : "No");
1931 }
1932 }
1933 }
1934
printSessionStats(tr_variant * top)1935 static void printSessionStats(tr_variant* top)
1936 {
1937 tr_variant* args;
1938 tr_variant* d;
1939
1940 if (tr_variantDictFindDict(top, TR_KEY_arguments, &args))
1941 {
1942 char buf[512];
1943 int64_t up;
1944 int64_t down;
1945 int64_t secs;
1946 int64_t sessions;
1947
1948 if (tr_variantDictFindDict(args, TR_KEY_current_stats, &d) && tr_variantDictFindInt(d, TR_KEY_uploadedBytes, &up) &&
1949 tr_variantDictFindInt(d, TR_KEY_downloadedBytes, &down) && tr_variantDictFindInt(d, TR_KEY_secondsActive, &secs))
1950 {
1951 printf("\nCURRENT SESSION\n");
1952 printf(" Uploaded: %s\n", strlsize(buf, up, sizeof(buf)));
1953 printf(" Downloaded: %s\n", strlsize(buf, down, sizeof(buf)));
1954 printf(" Ratio: %s\n", strlratio(buf, up, down, sizeof(buf)));
1955 printf(" Duration: %s\n", tr_strltime(buf, secs, sizeof(buf)));
1956 }
1957
1958 if (tr_variantDictFindDict(args, TR_KEY_cumulative_stats, &d) &&
1959 tr_variantDictFindInt(d, TR_KEY_sessionCount, &sessions) &&
1960 tr_variantDictFindInt(d, TR_KEY_uploadedBytes, &up) &&
1961 tr_variantDictFindInt(d, TR_KEY_downloadedBytes, &down) &&
1962 tr_variantDictFindInt(d, TR_KEY_secondsActive, &secs))
1963 {
1964 printf("\nTOTAL\n");
1965 printf(" Started %lu times\n", (unsigned long)sessions);
1966 printf(" Uploaded: %s\n", strlsize(buf, up, sizeof(buf)));
1967 printf(" Downloaded: %s\n", strlsize(buf, down, sizeof(buf)));
1968 printf(" Ratio: %s\n", strlratio(buf, up, down, sizeof(buf)));
1969 printf(" Duration: %s\n", tr_strltime(buf, secs, sizeof(buf)));
1970 }
1971 }
1972 }
1973
1974 static char id[4096];
1975
processResponse(char const * rpcurl,void const * response,size_t len)1976 static int processResponse(char const* rpcurl, void const* response, size_t len)
1977 {
1978 tr_variant top;
1979 int status = EXIT_SUCCESS;
1980
1981 if (debug)
1982 {
1983 fprintf(stderr, "got response (len %d):\n--------\n%*.*s\n--------\n", (int)len, (int)len, (int)len,
1984 (char const*)response);
1985 }
1986
1987 if (tr_variantFromJson(&top, response, len) != 0)
1988 {
1989 tr_logAddNamedError(MY_NAME, "Unable to parse response \"%*.*s\"", (int)len, (int)len, (char const*)response);
1990 status |= EXIT_FAILURE;
1991 }
1992 else
1993 {
1994 int64_t tag = -1;
1995 char const* str;
1996
1997 if (tr_variantDictFindStr(&top, TR_KEY_result, &str, NULL))
1998 {
1999 if (strcmp(str, "success") != 0)
2000 {
2001 printf("Error: %s\n", str);
2002 status |= EXIT_FAILURE;
2003 }
2004 else
2005 {
2006 tr_variantDictFindInt(&top, TR_KEY_tag, &tag);
2007
2008 switch (tag)
2009 {
2010 case TAG_SESSION:
2011 printSession(&top);
2012 break;
2013
2014 case TAG_STATS:
2015 printSessionStats(&top);
2016 break;
2017
2018 case TAG_DETAILS:
2019 printDetails(&top);
2020 break;
2021
2022 case TAG_FILES:
2023 printFileList(&top);
2024 break;
2025
2026 case TAG_LIST:
2027 printTorrentList(&top);
2028 break;
2029
2030 case TAG_PEERS:
2031 printPeers(&top);
2032 break;
2033
2034 case TAG_PIECES:
2035 printPieces(&top);
2036 break;
2037
2038 case TAG_PORTTEST:
2039 printPortTest(&top);
2040 break;
2041
2042 case TAG_TRACKERS:
2043 printTrackers(&top);
2044 break;
2045
2046 case TAG_TORRENT_ADD:
2047 {
2048 int64_t i;
2049 tr_variant* b = ⊤
2050
2051 if (tr_variantDictFindDict(&top, ARGUMENTS, &b) &&
2052 tr_variantDictFindDict(b, TR_KEY_torrent_added, &b) &&
2053 tr_variantDictFindInt(b, TR_KEY_id, &i))
2054 {
2055 tr_snprintf(id, sizeof(id), "%" PRId64, i);
2056 }
2057
2058 /* fall-through to default: to give success or failure msg */
2059 TR_GNUC_FALLTHROUGH;
2060 }
2061
2062 default:
2063 if (!tr_variantDictFindStr(&top, TR_KEY_result, &str, NULL))
2064 {
2065 status |= EXIT_FAILURE;
2066 }
2067 else
2068 {
2069 printf("%s responded: \"%s\"\n", rpcurl, str);
2070
2071 if (strcmp(str, "success") != 0)
2072 {
2073 status |= EXIT_FAILURE;
2074 }
2075 }
2076 }
2077
2078 tr_variantFree(&top);
2079 }
2080 }
2081 else
2082 {
2083 status |= EXIT_FAILURE;
2084 }
2085 }
2086
2087 return status;
2088 }
2089
tr_curl_easy_init(struct evbuffer * writebuf)2090 static CURL* tr_curl_easy_init(struct evbuffer* writebuf)
2091 {
2092 CURL* curl = curl_easy_init();
2093 curl_easy_setopt(curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING);
2094 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
2095 curl_easy_setopt(curl, CURLOPT_WRITEDATA, writebuf);
2096 curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, parseResponseHeader);
2097 curl_easy_setopt(curl, CURLOPT_POST, 1);
2098 curl_easy_setopt(curl, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
2099 curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
2100 curl_easy_setopt(curl, CURLOPT_VERBOSE, debug);
2101 curl_easy_setopt(curl, CURLOPT_ENCODING, ""); /* "" tells curl to fill in the blanks with what it was compiled to support */
2102
2103 if (netrc != NULL)
2104 {
2105 curl_easy_setopt(curl, CURLOPT_NETRC_FILE, netrc);
2106 }
2107
2108 if (auth != NULL)
2109 {
2110 curl_easy_setopt(curl, CURLOPT_USERPWD, auth);
2111 }
2112
2113 if (UseSSL)
2114 {
2115 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); /* do not verify subject/hostname */
2116 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); /* since most certs will be self-signed, do not verify against CA */
2117 }
2118
2119 if (sessionId != NULL)
2120 {
2121 char* h = tr_strdup_printf("%s: %s", TR_RPC_SESSION_ID_HEADER, sessionId);
2122 struct curl_slist* custom_headers = curl_slist_append(NULL, h);
2123 tr_free(h);
2124
2125 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, custom_headers);
2126 curl_easy_setopt(curl, CURLOPT_PRIVATE, custom_headers);
2127 }
2128
2129 return curl;
2130 }
2131
tr_curl_easy_cleanup(CURL * curl)2132 static void tr_curl_easy_cleanup(CURL* curl)
2133 {
2134 struct curl_slist* custom_headers = NULL;
2135 curl_easy_getinfo(curl, CURLINFO_PRIVATE, &custom_headers);
2136
2137 curl_easy_cleanup(curl);
2138
2139 if (custom_headers != NULL)
2140 {
2141 curl_slist_free_all(custom_headers);
2142 }
2143 }
2144
flush(char const * rpcurl,tr_variant ** benc)2145 static int flush(char const* rpcurl, tr_variant** benc)
2146 {
2147 CURLcode res;
2148 CURL* curl;
2149 int status = EXIT_SUCCESS;
2150 struct evbuffer* buf = evbuffer_new();
2151 char* json = tr_variantToStr(*benc, TR_VARIANT_FMT_JSON_LEAN, NULL);
2152 char* rpcurl_http = tr_strdup_printf(UseSSL ? "https://%s" : "http://%s", rpcurl);
2153
2154 curl = tr_curl_easy_init(buf);
2155 curl_easy_setopt(curl, CURLOPT_URL, rpcurl_http);
2156 curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json);
2157 curl_easy_setopt(curl, CURLOPT_TIMEOUT, getTimeoutSecs(json));
2158
2159 if (debug)
2160 {
2161 fprintf(stderr, "posting:\n--------\n%s\n--------\n", json);
2162 }
2163
2164 if ((res = curl_easy_perform(curl)) != CURLE_OK)
2165 {
2166 tr_logAddNamedError(MY_NAME, " (%s) %s", rpcurl_http, curl_easy_strerror(res));
2167 status |= EXIT_FAILURE;
2168 }
2169 else
2170 {
2171 long response;
2172 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
2173
2174 switch (response)
2175 {
2176 case 200:
2177 status |= processResponse(rpcurl, (char const*)evbuffer_pullup(buf, -1), evbuffer_get_length(buf));
2178 break;
2179
2180 case 409:
2181 /* Session id failed. Our curl header func has already
2182 * pulled the new session id from this response's headers,
2183 * build a new CURL* and try again */
2184 tr_curl_easy_cleanup(curl);
2185 curl = NULL;
2186 status |= flush(rpcurl, benc);
2187 benc = NULL;
2188 break;
2189
2190 default:
2191 evbuffer_add(buf, "", 1);
2192 fprintf(stderr, "Unexpected response: %s\n", evbuffer_pullup(buf, -1));
2193 status |= EXIT_FAILURE;
2194 break;
2195 }
2196 }
2197
2198 /* cleanup */
2199 tr_free(rpcurl_http);
2200 tr_free(json);
2201 evbuffer_free(buf);
2202
2203 if (curl != NULL)
2204 {
2205 tr_curl_easy_cleanup(curl);
2206 }
2207
2208 if (benc != NULL)
2209 {
2210 tr_variantFree(*benc);
2211 tr_free(*benc);
2212 *benc = NULL;
2213 }
2214
2215 return status;
2216 }
2217
ensure_sset(tr_variant ** sset)2218 static tr_variant* ensure_sset(tr_variant** sset)
2219 {
2220 tr_variant* args;
2221
2222 if (*sset != NULL)
2223 {
2224 args = tr_variantDictFind(*sset, ARGUMENTS);
2225 }
2226 else
2227 {
2228 *sset = tr_new0(tr_variant, 1);
2229 tr_variantInitDict(*sset, 3);
2230 tr_variantDictAddStr(*sset, TR_KEY_method, "session-set");
2231 args = tr_variantDictAddDict(*sset, ARGUMENTS, 0);
2232 }
2233
2234 return args;
2235 }
2236
ensure_tset(tr_variant ** tset)2237 static tr_variant* ensure_tset(tr_variant** tset)
2238 {
2239 tr_variant* args;
2240
2241 if (*tset != NULL)
2242 {
2243 args = tr_variantDictFind(*tset, ARGUMENTS);
2244 }
2245 else
2246 {
2247 *tset = tr_new0(tr_variant, 1);
2248 tr_variantInitDict(*tset, 3);
2249 tr_variantDictAddStr(*tset, TR_KEY_method, "torrent-set");
2250 args = tr_variantDictAddDict(*tset, ARGUMENTS, 1);
2251 }
2252
2253 return args;
2254 }
2255
processArgs(char const * rpcurl,int argc,char const * const * argv)2256 static int processArgs(char const* rpcurl, int argc, char const* const* argv)
2257 {
2258 int c;
2259 int status = EXIT_SUCCESS;
2260 char const* optarg;
2261 tr_variant* sset = NULL;
2262 tr_variant* tset = NULL;
2263 tr_variant* tadd = NULL;
2264
2265 *id = '\0';
2266
2267 while ((c = tr_getopt(getUsage(), argc, argv, opts, &optarg)) != TR_OPT_DONE)
2268 {
2269 int const stepMode = getOptMode(c);
2270
2271 if (stepMode == 0) /* meta commands */
2272 {
2273 switch (c)
2274 {
2275 case 'a': /* add torrent */
2276 if (sset != NULL)
2277 {
2278 status |= flush(rpcurl, &sset);
2279 }
2280
2281 if (tadd != NULL)
2282 {
2283 status |= flush(rpcurl, &tadd);
2284 }
2285
2286 if (tset != NULL)
2287 {
2288 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2289 status |= flush(rpcurl, &tset);
2290 }
2291
2292 tadd = tr_new0(tr_variant, 1);
2293 tr_variantInitDict(tadd, 3);
2294 tr_variantDictAddStr(tadd, TR_KEY_method, "torrent-add");
2295 tr_variantDictAddInt(tadd, TR_KEY_tag, TAG_TORRENT_ADD);
2296 tr_variantDictAddDict(tadd, ARGUMENTS, 0);
2297 break;
2298
2299 case 'b': /* debug */
2300 debug = true;
2301 break;
2302
2303 case 'n': /* auth */
2304 auth = tr_strdup(optarg);
2305 break;
2306
2307 case 810: /* authenv */
2308 auth = tr_env_get_string("TR_AUTH", NULL);
2309
2310 if (auth == NULL)
2311 {
2312 fprintf(stderr, "The TR_AUTH environment variable is not set\n");
2313 exit(0);
2314 }
2315
2316 break;
2317
2318 case 'N': /* netrc */
2319 netrc = tr_strdup(optarg);
2320 break;
2321
2322 case 820: /* UseSSL */
2323 UseSSL = true;
2324 break;
2325
2326 case 't': /* set current torrent */
2327 if (tadd != NULL)
2328 {
2329 status |= flush(rpcurl, &tadd);
2330 }
2331
2332 if (tset != NULL)
2333 {
2334 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2335 status |= flush(rpcurl, &tset);
2336 }
2337
2338 tr_strlcpy(id, optarg, sizeof(id));
2339 break;
2340
2341 case 'V': /* show version number */
2342 fprintf(stderr, "%s %s\n", MY_NAME, LONG_VERSION_STRING);
2343 exit(0);
2344 break;
2345
2346 case TR_OPT_ERR:
2347 fprintf(stderr, "invalid option\n");
2348 showUsage();
2349 status |= EXIT_FAILURE;
2350 break;
2351
2352 case TR_OPT_UNK:
2353 if (tadd != NULL)
2354 {
2355 tr_variant* args = tr_variantDictFind(tadd, ARGUMENTS);
2356 char* tmp = getEncodedMetainfo(optarg);
2357
2358 if (tmp != NULL)
2359 {
2360 tr_variantDictAddStr(args, TR_KEY_metainfo, tmp);
2361 }
2362 else
2363 {
2364 tr_variantDictAddStr(args, TR_KEY_filename, optarg);
2365 }
2366
2367 tr_free(tmp);
2368 }
2369 else
2370 {
2371 fprintf(stderr, "Unknown option: %s\n", optarg);
2372 status |= EXIT_FAILURE;
2373 }
2374
2375 break;
2376 }
2377 }
2378 else if (stepMode == MODE_TORRENT_GET)
2379 {
2380 tr_variant* top = tr_new0(tr_variant, 1);
2381 tr_variant* args;
2382 tr_variant* fields;
2383 tr_variantInitDict(top, 3);
2384 tr_variantDictAddStr(top, TR_KEY_method, "torrent-get");
2385 args = tr_variantDictAddDict(top, ARGUMENTS, 0);
2386 fields = tr_variantDictAddList(args, TR_KEY_fields, 0);
2387
2388 if (tset != NULL)
2389 {
2390 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2391 status |= flush(rpcurl, &tset);
2392 }
2393
2394 switch (c)
2395 {
2396 case 'i':
2397 tr_variantDictAddInt(top, TR_KEY_tag, TAG_DETAILS);
2398
2399 for (size_t i = 0; i < TR_N_ELEMENTS(details_keys); ++i)
2400 {
2401 tr_variantListAddQuark(fields, details_keys[i]);
2402 }
2403
2404 addIdArg(args, id, NULL);
2405 break;
2406
2407 case 'l':
2408 tr_variantDictAddInt(top, TR_KEY_tag, TAG_LIST);
2409
2410 for (size_t i = 0; i < TR_N_ELEMENTS(list_keys); ++i)
2411 {
2412 tr_variantListAddQuark(fields, list_keys[i]);
2413 }
2414
2415 addIdArg(args, id, "all");
2416 break;
2417
2418 case 940:
2419 tr_variantDictAddInt(top, TR_KEY_tag, TAG_FILES);
2420
2421 for (size_t i = 0; i < TR_N_ELEMENTS(files_keys); ++i)
2422 {
2423 tr_variantListAddQuark(fields, files_keys[i]);
2424 }
2425
2426 addIdArg(args, id, NULL);
2427 break;
2428
2429 case 941:
2430 tr_variantDictAddInt(top, TR_KEY_tag, TAG_PEERS);
2431 tr_variantListAddStr(fields, "peers");
2432 addIdArg(args, id, NULL);
2433 break;
2434
2435 case 942:
2436 tr_variantDictAddInt(top, TR_KEY_tag, TAG_PIECES);
2437 tr_variantListAddStr(fields, "pieces");
2438 tr_variantListAddStr(fields, "pieceCount");
2439 addIdArg(args, id, NULL);
2440 break;
2441
2442 case 943:
2443 tr_variantDictAddInt(top, TR_KEY_tag, TAG_TRACKERS);
2444 tr_variantListAddStr(fields, "trackerStats");
2445 addIdArg(args, id, NULL);
2446 break;
2447
2448 default:
2449 assert("unhandled value" && 0);
2450 }
2451
2452 status |= flush(rpcurl, &top);
2453 }
2454 else if (stepMode == MODE_SESSION_SET)
2455 {
2456 tr_variant* args = ensure_sset(&sset);
2457
2458 switch (c)
2459 {
2460 case 800:
2461 tr_variantDictAddStr(args, TR_KEY_script_torrent_done_filename, optarg);
2462 tr_variantDictAddBool(args, TR_KEY_script_torrent_done_enabled, true);
2463 break;
2464
2465 case 801:
2466 tr_variantDictAddBool(args, TR_KEY_script_torrent_done_enabled, false);
2467 break;
2468
2469 case 970:
2470 tr_variantDictAddBool(args, TR_KEY_alt_speed_enabled, true);
2471 break;
2472
2473 case 971:
2474 tr_variantDictAddBool(args, TR_KEY_alt_speed_enabled, false);
2475 break;
2476
2477 case 972:
2478 tr_variantDictAddInt(args, TR_KEY_alt_speed_down, numarg(optarg));
2479 break;
2480
2481 case 973:
2482 tr_variantDictAddInt(args, TR_KEY_alt_speed_up, numarg(optarg));
2483 break;
2484
2485 case 974:
2486 tr_variantDictAddBool(args, TR_KEY_alt_speed_time_enabled, true);
2487 break;
2488
2489 case 975:
2490 tr_variantDictAddBool(args, TR_KEY_alt_speed_time_enabled, false);
2491 break;
2492
2493 case 976:
2494 addTime(args, TR_KEY_alt_speed_time_begin, optarg);
2495 break;
2496
2497 case 977:
2498 addTime(args, TR_KEY_alt_speed_time_end, optarg);
2499 break;
2500
2501 case 978:
2502 addDays(args, TR_KEY_alt_speed_time_day, optarg);
2503 break;
2504
2505 case 'c':
2506 tr_variantDictAddStr(args, TR_KEY_incomplete_dir, optarg);
2507 tr_variantDictAddBool(args, TR_KEY_incomplete_dir_enabled, true);
2508 break;
2509
2510 case 'C':
2511 tr_variantDictAddBool(args, TR_KEY_incomplete_dir_enabled, false);
2512 break;
2513
2514 case 'e':
2515 tr_variantDictAddInt(args, TR_KEY_cache_size_mb, atoi(optarg));
2516 break;
2517
2518 case 910:
2519 tr_variantDictAddStr(args, TR_KEY_encryption, "required");
2520 break;
2521
2522 case 911:
2523 tr_variantDictAddStr(args, TR_KEY_encryption, "preferred");
2524 break;
2525
2526 case 912:
2527 tr_variantDictAddStr(args, TR_KEY_encryption, "tolerated");
2528 break;
2529
2530 case 'm':
2531 tr_variantDictAddBool(args, TR_KEY_port_forwarding_enabled, true);
2532 break;
2533
2534 case 'M':
2535 tr_variantDictAddBool(args, TR_KEY_port_forwarding_enabled, false);
2536 break;
2537
2538 case 'o':
2539 tr_variantDictAddBool(args, TR_KEY_dht_enabled, true);
2540 break;
2541
2542 case 'O':
2543 tr_variantDictAddBool(args, TR_KEY_dht_enabled, false);
2544 break;
2545
2546 case 830:
2547 tr_variantDictAddBool(args, TR_KEY_utp_enabled, true);
2548 break;
2549
2550 case 831:
2551 tr_variantDictAddBool(args, TR_KEY_utp_enabled, false);
2552 break;
2553
2554 case 'p':
2555 tr_variantDictAddInt(args, TR_KEY_peer_port, numarg(optarg));
2556 break;
2557
2558 case 'P':
2559 tr_variantDictAddBool(args, TR_KEY_peer_port_random_on_start, true);
2560 break;
2561
2562 case 'x':
2563 tr_variantDictAddBool(args, TR_KEY_pex_enabled, true);
2564 break;
2565
2566 case 'X':
2567 tr_variantDictAddBool(args, TR_KEY_pex_enabled, false);
2568 break;
2569
2570 case 'y':
2571 tr_variantDictAddBool(args, TR_KEY_lpd_enabled, true);
2572 break;
2573
2574 case 'Y':
2575 tr_variantDictAddBool(args, TR_KEY_lpd_enabled, false);
2576 break;
2577
2578 case 953:
2579 tr_variantDictAddReal(args, TR_KEY_seedRatioLimit, atof(optarg));
2580 tr_variantDictAddBool(args, TR_KEY_seedRatioLimited, true);
2581 break;
2582
2583 case 954:
2584 tr_variantDictAddBool(args, TR_KEY_seedRatioLimited, false);
2585 break;
2586
2587 case 990:
2588 tr_variantDictAddBool(args, TR_KEY_start_added_torrents, false);
2589 break;
2590
2591 case 991:
2592 tr_variantDictAddBool(args, TR_KEY_start_added_torrents, true);
2593 break;
2594
2595 case 992:
2596 tr_variantDictAddBool(args, TR_KEY_trash_original_torrent_files, true);
2597 break;
2598
2599 case 993:
2600 tr_variantDictAddBool(args, TR_KEY_trash_original_torrent_files, false);
2601 break;
2602
2603 default:
2604 assert("unhandled value" && 0);
2605 break;
2606 }
2607 }
2608 else if (stepMode == (MODE_SESSION_SET | MODE_TORRENT_SET))
2609 {
2610 tr_variant* targs = NULL;
2611 tr_variant* sargs = NULL;
2612
2613 if (!tr_str_is_empty(id))
2614 {
2615 targs = ensure_tset(&tset);
2616 }
2617 else
2618 {
2619 sargs = ensure_sset(&sset);
2620 }
2621
2622 switch (c)
2623 {
2624 case 'd':
2625 if (targs != NULL)
2626 {
2627 tr_variantDictAddInt(targs, TR_KEY_downloadLimit, numarg(optarg));
2628 tr_variantDictAddBool(targs, TR_KEY_downloadLimited, true);
2629 }
2630 else
2631 {
2632 tr_variantDictAddInt(sargs, TR_KEY_speed_limit_down, numarg(optarg));
2633 tr_variantDictAddBool(sargs, TR_KEY_speed_limit_down_enabled, true);
2634 }
2635
2636 break;
2637
2638 case 'D':
2639 if (targs != NULL)
2640 {
2641 tr_variantDictAddBool(targs, TR_KEY_downloadLimited, false);
2642 }
2643 else
2644 {
2645 tr_variantDictAddBool(sargs, TR_KEY_speed_limit_down_enabled, false);
2646 }
2647
2648 break;
2649
2650 case 'u':
2651 if (targs != NULL)
2652 {
2653 tr_variantDictAddInt(targs, TR_KEY_uploadLimit, numarg(optarg));
2654 tr_variantDictAddBool(targs, TR_KEY_uploadLimited, true);
2655 }
2656 else
2657 {
2658 tr_variantDictAddInt(sargs, TR_KEY_speed_limit_up, numarg(optarg));
2659 tr_variantDictAddBool(sargs, TR_KEY_speed_limit_up_enabled, true);
2660 }
2661
2662 break;
2663
2664 case 'U':
2665 if (targs != NULL)
2666 {
2667 tr_variantDictAddBool(targs, TR_KEY_uploadLimited, false);
2668 }
2669 else
2670 {
2671 tr_variantDictAddBool(sargs, TR_KEY_speed_limit_up_enabled, false);
2672 }
2673
2674 break;
2675
2676 case 930:
2677 if (targs != NULL)
2678 {
2679 tr_variantDictAddInt(targs, TR_KEY_peer_limit, atoi(optarg));
2680 }
2681 else
2682 {
2683 tr_variantDictAddInt(sargs, TR_KEY_peer_limit_global, atoi(optarg));
2684 }
2685
2686 break;
2687
2688 default:
2689 assert("unhandled value" && 0);
2690 break;
2691 }
2692 }
2693 else if (stepMode == MODE_TORRENT_SET)
2694 {
2695 tr_variant* args = ensure_tset(&tset);
2696
2697 switch (c)
2698 {
2699 case 'L':
2700 addLabels(args, optarg);
2701 break;
2702
2703 case 712:
2704 tr_variantListAddInt(tr_variantDictAddList(args, TR_KEY_trackerRemove, 1), atoi(optarg));
2705 break;
2706
2707 case 950:
2708 tr_variantDictAddReal(args, TR_KEY_seedRatioLimit, atof(optarg));
2709 tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_SINGLE);
2710 break;
2711
2712 case 951:
2713 tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_GLOBAL);
2714 break;
2715
2716 case 952:
2717 tr_variantDictAddInt(args, TR_KEY_seedRatioMode, TR_RATIOLIMIT_UNLIMITED);
2718 break;
2719
2720 case 984:
2721 tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, true);
2722 break;
2723
2724 case 985:
2725 tr_variantDictAddBool(args, TR_KEY_honorsSessionLimits, false);
2726 break;
2727
2728 default:
2729 assert("unhandled value" && 0);
2730 break;
2731 }
2732 }
2733 else if (stepMode == (MODE_TORRENT_SET | MODE_TORRENT_ADD))
2734 {
2735 tr_variant* args;
2736
2737 if (tadd != NULL)
2738 {
2739 args = tr_variantDictFind(tadd, ARGUMENTS);
2740 }
2741 else
2742 {
2743 args = ensure_tset(&tset);
2744 }
2745
2746 switch (c)
2747 {
2748 case 'g':
2749 addFiles(args, TR_KEY_files_wanted, optarg);
2750 break;
2751
2752 case 'G':
2753 addFiles(args, TR_KEY_files_unwanted, optarg);
2754 break;
2755
2756 case 900:
2757 addFiles(args, TR_KEY_priority_high, optarg);
2758 break;
2759
2760 case 901:
2761 addFiles(args, TR_KEY_priority_normal, optarg);
2762 break;
2763
2764 case 902:
2765 addFiles(args, TR_KEY_priority_low, optarg);
2766 break;
2767
2768 case 700:
2769 tr_variantDictAddInt(args, TR_KEY_bandwidthPriority, 1);
2770 break;
2771
2772 case 701:
2773 tr_variantDictAddInt(args, TR_KEY_bandwidthPriority, 0);
2774 break;
2775
2776 case 702:
2777 tr_variantDictAddInt(args, TR_KEY_bandwidthPriority, -1);
2778 break;
2779
2780 case 710:
2781 tr_variantListAddStr(tr_variantDictAddList(args, TR_KEY_trackerAdd, 1), optarg);
2782 break;
2783
2784 default:
2785 assert("unhandled value" && 0);
2786 break;
2787 }
2788 }
2789 else if (c == 961) /* set location */
2790 {
2791 if (tadd != NULL)
2792 {
2793 tr_variant* args = tr_variantDictFind(tadd, ARGUMENTS);
2794 tr_variantDictAddStr(args, TR_KEY_download_dir, optarg);
2795 }
2796 else
2797 {
2798 tr_variant* args;
2799 tr_variant* top = tr_new0(tr_variant, 1);
2800 tr_variantInitDict(top, 2);
2801 tr_variantDictAddStr(top, TR_KEY_method, "torrent-set-location");
2802 args = tr_variantDictAddDict(top, ARGUMENTS, 3);
2803 tr_variantDictAddStr(args, TR_KEY_location, optarg);
2804 tr_variantDictAddBool(args, TR_KEY_move, false);
2805 addIdArg(args, id, NULL);
2806 status |= flush(rpcurl, &top);
2807 break;
2808 }
2809 }
2810 else
2811 {
2812 switch (c)
2813 {
2814 case 920: /* session-info */
2815 {
2816 tr_variant* top = tr_new0(tr_variant, 1);
2817 tr_variantInitDict(top, 2);
2818 tr_variantDictAddStr(top, TR_KEY_method, "session-get");
2819 tr_variantDictAddInt(top, TR_KEY_tag, TAG_SESSION);
2820 status |= flush(rpcurl, &top);
2821 break;
2822 }
2823
2824 case 's': /* start */
2825 {
2826 if (tadd != NULL)
2827 {
2828 tr_variantDictAddBool(tr_variantDictFind(tadd, TR_KEY_arguments), TR_KEY_paused, false);
2829 }
2830 else
2831 {
2832 tr_variant* top = tr_new0(tr_variant, 1);
2833 tr_variantInitDict(top, 2);
2834 tr_variantDictAddStr(top, TR_KEY_method, "torrent-start");
2835 addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2836 status |= flush(rpcurl, &top);
2837 }
2838
2839 break;
2840 }
2841
2842 case 'S': /* stop */
2843 {
2844 if (tadd != NULL)
2845 {
2846 tr_variantDictAddBool(tr_variantDictFind(tadd, TR_KEY_arguments), TR_KEY_paused, true);
2847 }
2848 else
2849 {
2850 tr_variant* top = tr_new0(tr_variant, 1);
2851 tr_variantInitDict(top, 2);
2852 tr_variantDictAddStr(top, TR_KEY_method, "torrent-stop");
2853 addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2854 status |= flush(rpcurl, &top);
2855 }
2856
2857 break;
2858 }
2859
2860 case 'w':
2861 {
2862 tr_variant* args = tadd != NULL ? tr_variantDictFind(tadd, TR_KEY_arguments) : ensure_sset(&sset);
2863 tr_variantDictAddStr(args, TR_KEY_download_dir, optarg);
2864 break;
2865 }
2866
2867 case 850:
2868 {
2869 tr_variant* top = tr_new0(tr_variant, 1);
2870 tr_variantInitDict(top, 1);
2871 tr_variantDictAddStr(top, TR_KEY_method, "session-close");
2872 status |= flush(rpcurl, &top);
2873 break;
2874 }
2875
2876 case 963:
2877 {
2878 tr_variant* top = tr_new0(tr_variant, 1);
2879 tr_variantInitDict(top, 1);
2880 tr_variantDictAddStr(top, TR_KEY_method, "blocklist-update");
2881 status |= flush(rpcurl, &top);
2882 break;
2883 }
2884
2885 case 921:
2886 {
2887 tr_variant* top = tr_new0(tr_variant, 1);
2888 tr_variantInitDict(top, 2);
2889 tr_variantDictAddStr(top, TR_KEY_method, "session-stats");
2890 tr_variantDictAddInt(top, TR_KEY_tag, TAG_STATS);
2891 status |= flush(rpcurl, &top);
2892 break;
2893 }
2894
2895 case 962:
2896 {
2897 tr_variant* top = tr_new0(tr_variant, 1);
2898 tr_variantInitDict(top, 2);
2899 tr_variantDictAddStr(top, TR_KEY_method, "port-test");
2900 tr_variantDictAddInt(top, TR_KEY_tag, TAG_PORTTEST);
2901 status |= flush(rpcurl, &top);
2902 break;
2903 }
2904
2905 case 600:
2906 {
2907 tr_variant* top;
2908
2909 if (tset != NULL)
2910 {
2911 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2912 status |= flush(rpcurl, &tset);
2913 }
2914
2915 top = tr_new0(tr_variant, 1);
2916 tr_variantInitDict(top, 2);
2917 tr_variantDictAddStr(top, TR_KEY_method, "torrent-reannounce");
2918 addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2919 status |= flush(rpcurl, &top);
2920 break;
2921 }
2922
2923 case 'v':
2924 {
2925 tr_variant* top;
2926
2927 if (tset != NULL)
2928 {
2929 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2930 status |= flush(rpcurl, &tset);
2931 }
2932
2933 top = tr_new0(tr_variant, 1);
2934 tr_variantInitDict(top, 2);
2935 tr_variantDictAddStr(top, TR_KEY_method, "torrent-verify");
2936 addIdArg(tr_variantDictAddDict(top, ARGUMENTS, 1), id, NULL);
2937 status |= flush(rpcurl, &top);
2938 break;
2939 }
2940
2941 case 'r':
2942 case 840:
2943 {
2944 tr_variant* args;
2945 tr_variant* top = tr_new0(tr_variant, 1);
2946 tr_variantInitDict(top, 2);
2947 tr_variantDictAddStr(top, TR_KEY_method, "torrent-remove");
2948 args = tr_variantDictAddDict(top, ARGUMENTS, 2);
2949 tr_variantDictAddBool(args, TR_KEY_delete_local_data, c == 840);
2950 addIdArg(args, id, NULL);
2951 status |= flush(rpcurl, &top);
2952 break;
2953 }
2954
2955 case 960:
2956 {
2957 tr_variant* args;
2958 tr_variant* top = tr_new0(tr_variant, 1);
2959 tr_variantInitDict(top, 2);
2960 tr_variantDictAddStr(top, TR_KEY_method, "torrent-set-location");
2961 args = tr_variantDictAddDict(top, ARGUMENTS, 3);
2962 tr_variantDictAddStr(args, TR_KEY_location, optarg);
2963 tr_variantDictAddBool(args, TR_KEY_move, true);
2964 addIdArg(args, id, NULL);
2965 status |= flush(rpcurl, &top);
2966 break;
2967 }
2968
2969 default:
2970 {
2971 fprintf(stderr, "got opt [%d]\n", c);
2972 showUsage();
2973 break;
2974 }
2975 }
2976 }
2977 }
2978
2979 if (tadd != NULL)
2980 {
2981 status |= flush(rpcurl, &tadd);
2982 }
2983
2984 if (tset != NULL)
2985 {
2986 addIdArg(tr_variantDictFind(tset, ARGUMENTS), id, NULL);
2987 status |= flush(rpcurl, &tset);
2988 }
2989
2990 if (sset != NULL)
2991 {
2992 status |= flush(rpcurl, &sset);
2993 }
2994
2995 return status;
2996 }
2997
2998 /* [host:port] or [host] or [port] or [http(s?)://host:port/transmission/] */
getHostAndPortAndRpcUrl(int * argc,char ** argv,char ** host,int * port,char ** rpcurl)2999 static void getHostAndPortAndRpcUrl(int* argc, char** argv, char** host, int* port, char** rpcurl)
3000 {
3001 if (*argv[1] == '-')
3002 {
3003 return;
3004 }
3005
3006 char const* const s = argv[1];
3007
3008 if (strncmp(s, "http://", 7) == 0) /* user passed in http rpc url */
3009 {
3010 *rpcurl = tr_strdup_printf("%s/rpc/", s + 7);
3011 }
3012 else if (strncmp(s, "https://", 8) == 0) /* user passed in https rpc url */
3013 {
3014 UseSSL = true;
3015 *rpcurl = tr_strdup_printf("%s/rpc/", s + 8);
3016 }
3017 else
3018 {
3019 char const* const first_colon = strchr(s, ':');
3020 char const* const last_colon = strrchr(s, ':');
3021
3022 if (last_colon != NULL && ((*s == '[' && *(last_colon - 1) == ']') || first_colon == last_colon))
3023 {
3024 /* user passed in both host and port */
3025 *host = tr_strndup(s, last_colon - s);
3026 *port = atoi(last_colon + 1);
3027 }
3028 else
3029 {
3030 char* end;
3031 int const i = strtol(s, &end, 10);
3032
3033 if (*end == '\0') /* user passed in a port */
3034 {
3035 *port = i;
3036 }
3037 else /* user passed in a host */
3038 {
3039 if (last_colon != NULL && first_colon != last_colon && (*s != '[' || *(s + strlen(s) - 1) != ']'))
3040 {
3041 /* looks like IPv6; let's add brackets as we'll be appending the port later on */
3042 *host = tr_strdup_printf("[%s]", s);
3043 }
3044 else
3045 {
3046 *host = tr_strdup(s);
3047 }
3048 }
3049 }
3050 }
3051
3052 *argc -= 1;
3053
3054 for (int i = 1; i < *argc; ++i)
3055 {
3056 argv[i] = argv[i + 1];
3057 }
3058 }
3059
tr_main(int argc,char * argv[])3060 int tr_main(int argc, char* argv[])
3061 {
3062 int port = DEFAULT_PORT;
3063 char* host = NULL;
3064 char* rpcurl = NULL;
3065 int exit_status = EXIT_SUCCESS;
3066
3067 if (argc < 2)
3068 {
3069 showUsage();
3070 return EXIT_FAILURE;
3071 }
3072
3073 tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
3074 tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
3075 tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
3076
3077 getHostAndPortAndRpcUrl(&argc, argv, &host, &port, &rpcurl);
3078
3079 if (host == NULL)
3080 {
3081 host = tr_strdup(DEFAULT_HOST);
3082 }
3083
3084 if (rpcurl == NULL)
3085 {
3086 rpcurl = tr_strdup_printf("%s:%d%s", host, port, DEFAULT_URL);
3087 }
3088
3089 exit_status = processArgs(rpcurl, argc, (char const* const*)argv);
3090
3091 tr_free(host);
3092 tr_free(rpcurl);
3093 return exit_status;
3094 }
3095