source: trunk/cli/cli.c @ 13764

Last change on this file since 13764 was 13764, checked in by jordan, 8 years ago

(cli) #5212 'cli fails when download directory doesn't exist' -- fixed.

  • Property svn:keywords set to Date Rev Author Id
File size: 14.5 KB
Line 
1/******************************************************************************
2 * $Id: cli.c 13764 2013-01-04 23:58:52Z jordan $
3 *
4 * Copyright (c) Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <stdio.h> /* fprintf () */
26#include <stdlib.h> /* atoi () */
27#include <string.h> /* memcmp () */
28#include <signal.h>
29
30#include <libtransmission/transmission.h>
31#include <libtransmission/tr-getopt.h>
32#include <libtransmission/utils.h> /* tr_wait_msec */
33#include <libtransmission/variant.h>
34#include <libtransmission/version.h>
35#include <libtransmission/web.h> /* tr_webRun */
36
37/***
38****
39***/
40
41#define MEM_K 1024
42#define MEM_K_STR "KiB"
43#define MEM_M_STR "MiB"
44#define MEM_G_STR "GiB"
45#define MEM_T_STR "TiB"
46
47#define DISK_K 1000
48#define DISK_B_STR   "B"
49#define DISK_K_STR "kB"
50#define DISK_M_STR "MB"
51#define DISK_G_STR "GB"
52#define DISK_T_STR "TB"
53
54#define SPEED_K 1000
55#define SPEED_B_STR  "B/s"
56#define SPEED_K_STR "kB/s"
57#define SPEED_M_STR "MB/s"
58#define SPEED_G_STR "GB/s"
59#define SPEED_T_STR "TB/s"
60
61/***
62****
63***/
64
65#define LINEWIDTH 80
66#define MY_CONFIG_NAME "transmission"
67#define MY_READABLE_NAME "transmission-cli"
68
69static bool showVersion              = false;
70static bool verify                   = false;
71static sig_atomic_t gotsig           = false;
72static sig_atomic_t manualUpdate     = false;
73
74static const char * torrentPath  = NULL;
75
76static const struct tr_option options[] =
77{
78  { 'b', "blocklist",            "Enable peer blocklists", "b",  0, NULL },
79  { 'B', "no-blocklist",         "Disable peer blocklists", "B",  0, NULL },
80  { 'd', "downlimit",            "Set max download speed in "SPEED_K_STR, "d",  1, "<speed>" },
81  { 'D', "no-downlimit",         "Don't limit the download speed", "D",  0, NULL },
82  { 910, "encryption-required",  "Encrypt all peer connections", "er", 0, NULL },
83  { 911, "encryption-preferred", "Prefer encrypted peer connections", "ep", 0, NULL },
84  { 912, "encryption-tolerated", "Prefer unencrypted peer connections", "et", 0, NULL },
85  { 'f', "finish",               "Run a script when the torrent finishes", "f", 1, "<script>" },
86  { 'g', "config-dir",           "Where to find configuration files", "g", 1, "<path>" },
87  { 'm', "portmap",              "Enable portmapping via NAT-PMP or UPnP", "m",  0, NULL },
88  { 'M', "no-portmap",           "Disable portmapping", "M",  0, NULL },
89  { 'p', "port", "Port for incoming peers (Default: " TR_DEFAULT_PEER_PORT_STR ")", "p", 1, "<port>" },
90  { 't', "tos", "Peer socket TOS (0 to 255, default=" TR_DEFAULT_PEER_SOCKET_TOS_STR ")", "t", 1, "<tos>" },
91  { 'u', "uplimit",              "Set max upload speed in "SPEED_K_STR, "u",  1, "<speed>"   },
92  { 'U', "no-uplimit",           "Don't limit the upload speed", "U",  0, NULL        },
93  { 'v', "verify",               "Verify the specified torrent", "v",  0, NULL        },
94  { 'V', "version",              "Show version number and exit", "V", 0, NULL },
95  { 'w', "download-dir",         "Where to save downloaded data", "w",  1, "<path>"    },
96  { 0, NULL, NULL, NULL, 0, NULL }
97};
98
99static const char *
100getUsage (void)
101{
102  return "A fast and easy BitTorrent client\n"
103         "\n"
104         "Usage: " MY_READABLE_NAME " [options] <file|url|magnet>";
105}
106
107static int parseCommandLine (tr_variant*, int argc, const char ** argv);
108
109static void sigHandler (int signal);
110
111static char*
112tr_strlratio (char * buf, double ratio, size_t buflen)
113{
114  if ((int)ratio == TR_RATIO_NA)
115    tr_strlcpy (buf, _ ("None"), buflen);
116  else if ((int)ratio == TR_RATIO_INF)
117    tr_strlcpy (buf, "Inf", buflen);
118  else if (ratio < 10.0)
119    tr_snprintf (buf, buflen, "%.2f", ratio);
120  else if (ratio < 100.0)
121    tr_snprintf (buf, buflen, "%.1f", ratio);
122  else
123    tr_snprintf (buf, buflen, "%.0f", ratio);
124
125  return buf;
126}
127
128static bool waitingOnWeb;
129
130static void
131onTorrentFileDownloaded (tr_session   * session UNUSED,
132                         bool           did_connect UNUSED,
133                         bool           did_timeout UNUSED,
134                         long           response_code UNUSED,
135                         const void   * response,
136                         size_t         response_byte_count,
137                         void         * ctor)
138{
139  tr_ctorSetMetainfo (ctor, response, response_byte_count);
140  waitingOnWeb = false;
141}
142
143static void
144getStatusStr (const tr_stat * st,
145              char *          buf,
146              size_t          buflen)
147{
148  if (st->activity == TR_STATUS_CHECK_WAIT)
149    {
150      tr_snprintf (buf, buflen, "Waiting to verify local files");
151    }
152  else if (st->activity == TR_STATUS_CHECK)
153    {
154      tr_snprintf (buf, buflen,
155                   "Verifying local files (%.2f%%, %.2f%% valid)",
156                   tr_truncd (100 * st->recheckProgress, 2),
157                   tr_truncd (100 * st->percentDone, 2));
158    }
159  else if (st->activity == TR_STATUS_DOWNLOAD)
160    {
161      char upStr[80];
162      char dnStr[80];
163      char ratioStr[80];
164
165      tr_formatter_speed_KBps (upStr, st->pieceUploadSpeed_KBps, sizeof (upStr));
166      tr_formatter_speed_KBps (dnStr, st->pieceDownloadSpeed_KBps, sizeof (dnStr));
167      tr_strlratio (ratioStr, st->ratio, sizeof (ratioStr));
168
169      tr_snprintf (buf, buflen,
170                   "Progress: %.1f%%, "
171                   "dl from %d of %d peers (%s), "
172                   "ul to %d (%s) "
173                   "[%s]",
174                   tr_truncd (100 * st->percentDone, 1),
175                   st->peersSendingToUs, st->peersConnected, dnStr,
176                   st->peersGettingFromUs, upStr,
177                   ratioStr);
178    }
179  else if (st->activity == TR_STATUS_SEED)
180    {
181      char upStr[80];
182      char ratioStr[80];
183
184      tr_formatter_speed_KBps (upStr, st->pieceUploadSpeed_KBps, sizeof (upStr));
185      tr_strlratio (ratioStr, st->ratio, sizeof (ratioStr));
186
187      tr_snprintf (buf, buflen,
188                   "Seeding, uploading to %d of %d peer (s), %s [%s]",
189                   st->peersGettingFromUs, st->peersConnected, upStr, ratioStr);
190    }
191  else
192    {
193      *buf = '\0';
194    }
195}
196
197static const char*
198getConfigDir (int argc, const char ** argv)
199{
200  int c;
201  const char * configDir = NULL;
202  const char * optarg;
203  const int ind = tr_optind;
204
205  while ((c = tr_getopt (getUsage (), argc, argv, options, &optarg)))
206    {
207      if (c == 'g')
208        {
209          configDir = optarg;
210          break;
211        }
212    }
213
214  tr_optind = ind;
215
216  if (configDir == NULL)
217    configDir = tr_getDefaultConfigDir (MY_CONFIG_NAME);
218
219  return configDir;
220}
221
222int
223main (int argc, char ** argv)
224{
225  int           error;
226  tr_session  * h;
227  tr_ctor     * ctor;
228  tr_torrent  * tor = NULL;
229  tr_variant       settings;
230  const char  * configDir;
231  uint8_t     * fileContents;
232  size_t        fileLength;
233  const char  * str;
234
235  tr_formatter_mem_init (MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
236  tr_formatter_size_init (DISK_K,DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
237  tr_formatter_speed_init (SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
238
239  printf ("%s %s\n", MY_READABLE_NAME, LONG_VERSION_STRING);
240
241  /* user needs to pass in at least one argument */
242  if (argc < 2)
243    {
244      tr_getopt_usage (MY_READABLE_NAME, getUsage (), options);
245      return EXIT_FAILURE;
246    }
247
248  /* load the defaults from config file + libtransmission defaults */
249  tr_variantInitDict (&settings, 0);
250  configDir = getConfigDir (argc, (const char**)argv);
251  tr_sessionLoadSettings (&settings, configDir, MY_CONFIG_NAME);
252
253  /* the command line overrides defaults */
254  if (parseCommandLine (&settings, argc, (const char**)argv))
255    return EXIT_FAILURE;
256
257  if (showVersion)
258    return EXIT_SUCCESS;
259
260  /* Check the options for validity */
261  if (!torrentPath)
262    {
263      fprintf (stderr, "No torrent specified!\n");
264      return EXIT_FAILURE;
265    }
266
267  if (tr_variantDictFindStr (&settings, TR_KEY_download_dir, &str, NULL))
268    {
269      if (!tr_fileExists (str, NULL))
270        tr_mkdirp (str, 0700);
271
272      if (tr_fileExists (str, NULL))
273        {
274          tr_variantDictAddStr (&settings, TR_KEY_download_dir, str);
275        }
276      else
277        {
278          fprintf (stderr, "Unable to create download directory \"%s\"!\n", str);
279          return EXIT_FAILURE;
280        }
281    }
282
283  h = tr_sessionInit ("cli", configDir, false, &settings);
284
285  ctor = tr_ctorNew (h);
286
287  fileContents = tr_loadFile (torrentPath, &fileLength);
288  tr_ctorSetPaused (ctor, TR_FORCE, false);
289  if (fileContents != NULL)
290    {
291      tr_ctorSetMetainfo (ctor, fileContents, fileLength);
292    }
293  else if (!memcmp (torrentPath, "magnet:?", 8))
294    {
295      tr_ctorSetMetainfoFromMagnetLink (ctor, torrentPath);
296    }
297  else if (!memcmp (torrentPath, "http", 4))
298    {
299      tr_webRun (h, torrentPath, NULL, NULL, onTorrentFileDownloaded, ctor);
300      waitingOnWeb = true;
301      while (waitingOnWeb)
302        tr_wait_msec (1000);
303    }
304  else
305    {
306      fprintf (stderr, "ERROR: Unrecognized torrent \"%s\".\n", torrentPath);
307      fprintf (stderr, " * If you're trying to create a torrent, use transmission-create.\n");
308      fprintf (stderr, " * If you're trying to see a torrent's info, use transmission-show.\n");
309      tr_sessionClose (h);
310      return EXIT_FAILURE;
311    }
312
313  tr_free (fileContents);
314
315  tor = tr_torrentNew (ctor, &error);
316  tr_ctorFree (ctor);
317  if (!tor)
318    {
319      fprintf (stderr, "Failed opening torrent file `%s'\n", torrentPath);
320      tr_sessionClose (h);
321      return EXIT_FAILURE;
322    }
323
324  signal (SIGINT, sigHandler);
325#ifndef WIN32
326  signal (SIGHUP, sigHandler);
327#endif
328  tr_torrentStart (tor);
329
330  if (verify)
331    {
332      verify = false;
333      tr_torrentVerify (tor);
334    }
335
336  for (;;)
337    {
338      char  line[LINEWIDTH];
339      const tr_stat * st;
340      const char * messageName[] = { NULL, "Tracker gave a warning:",
341                                           "Tracker gave an error:",
342                                           "Error:" };
343
344      tr_wait_msec (200);
345
346      if (gotsig)
347        {
348          gotsig = false;
349          printf ("\nStopping torrent...\n");
350          tr_torrentStop (tor);
351        }
352
353      if (manualUpdate)
354        {
355          manualUpdate = false;
356          if (!tr_torrentCanManualUpdate (tor))
357            {
358              fprintf (stderr, "\nReceived SIGHUP, but can't send a manual update now\n");
359            }
360          else
361            {
362              fprintf (stderr, "\nReceived SIGHUP: manual update scheduled\n");
363              tr_torrentManualUpdate (tor);
364            }
365        }
366
367      st = tr_torrentStat (tor);
368      if (st->activity == TR_STATUS_STOPPED)
369        break;
370
371      getStatusStr (st, line, sizeof (line));
372      printf ("\r%-*s", LINEWIDTH, line);
373
374      if (messageName[st->error])
375        fprintf (stderr, "\n%s: %s\n", messageName[st->error], st->errorString);
376    }
377
378  tr_sessionSaveSettings (h, configDir, &settings);
379
380  printf ("\n");
381  tr_variantFree (&settings);
382  tr_sessionClose (h);
383  return EXIT_SUCCESS;
384}
385
386/***
387****
388****
389***/
390
391static int
392parseCommandLine (tr_variant * d, int argc, const char ** argv)
393{
394  int c;
395  const char * optarg;
396
397  while ((c = tr_getopt (getUsage (), argc, argv, options, &optarg)))
398    {
399      switch (c)
400        {
401          case 'b':
402            tr_variantDictAddBool (d, TR_KEY_blocklist_enabled, true);
403            break;
404
405          case 'B': tr_variantDictAddBool (d, TR_KEY_blocklist_enabled, false);
406            break;
407
408          case 'd':
409            tr_variantDictAddInt (d, TR_KEY_speed_limit_down, atoi (optarg));
410            tr_variantDictAddBool (d, TR_KEY_speed_limit_down_enabled, true);
411            break;
412
413          case 'D': tr_variantDictAddBool (d, TR_KEY_speed_limit_down_enabled, false);
414            break;
415
416          case 'f':
417            tr_variantDictAddStr (d, TR_KEY_script_torrent_done_filename, optarg);
418            tr_variantDictAddBool (d, TR_KEY_script_torrent_done_enabled, true);
419            break;
420
421          case 'g': /* handled above */
422            break;
423
424          case 'm':
425            tr_variantDictAddBool (d, TR_KEY_port_forwarding_enabled, true);
426            break;
427
428          case 'M':
429            tr_variantDictAddBool (d, TR_KEY_port_forwarding_enabled, false);
430            break;
431
432          case 'p':
433            tr_variantDictAddInt (d, TR_KEY_peer_port, atoi (optarg));
434            break;
435
436          case 't':
437            tr_variantDictAddInt (d, TR_KEY_peer_socket_tos, atoi (optarg));
438            break;
439
440          case 'u':
441            tr_variantDictAddInt (d, TR_KEY_speed_limit_up, atoi (optarg));
442            tr_variantDictAddBool (d, TR_KEY_speed_limit_up_enabled, true);
443            break;
444
445          case 'U':
446            tr_variantDictAddBool (d, TR_KEY_speed_limit_up_enabled, false);
447            break;
448
449          case 'v':
450            verify = true;
451            break;
452
453          case 'V':
454            showVersion = true;
455            break;
456
457          case 'w':
458            tr_variantDictAddStr (d, TR_KEY_download_dir, optarg);
459            break;
460
461          case 910:
462            tr_variantDictAddInt (d, TR_KEY_encryption, TR_ENCRYPTION_REQUIRED);
463            break;
464
465          case 911:
466            tr_variantDictAddInt (d, TR_KEY_encryption, TR_ENCRYPTION_PREFERRED);
467            break;
468
469          case 912:
470            tr_variantDictAddInt (d, TR_KEY_encryption, TR_CLEAR_PREFERRED);
471            break;
472
473          case TR_OPT_UNK:
474            if (torrentPath == NULL)
475              torrentPath = optarg;
476            break;
477
478          default:
479            return 1;
480        }
481    }
482
483  return 0;
484}
485
486static void
487sigHandler (int signal)
488{
489  switch (signal)
490    {
491      case SIGINT:
492        gotsig = true;
493        break;
494
495#ifndef WIN32
496      case SIGHUP:
497        manualUpdate = true;
498        break;
499
500#endif
501      default:
502        break;
503    }
504}
505
Note: See TracBrowser for help on using the repository browser.