source: trunk/daemon/remote.c @ 13672

Last change on this file since 13672 was 13672, checked in by jordan, 9 years ago

(trunk, libT) faster JSON parsing for tr_variant. This mostly helps the Qt client, which makes heavy use of the JSON-based RPC calls.

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