source: trunk/utils/edit.c @ 13840

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

(transmission-edit) fix global search-and-replace damage wreaked upon the --help text, reported by Elbandi

  • Property svn:keywords set to Date Rev Author Id
File size: 9.7 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: edit.c 13840 2013-01-22 18:59:39Z jordan $
11 */
12
13#include <stdio.h> /* fprintf () */
14#include <string.h> /* strlen (), strstr (), strcmp () */
15#include <stdlib.h> /* EXIT_FAILURE */
16
17#include <event2/buffer.h>
18
19#include <libtransmission/transmission.h>
20#include <libtransmission/tr-getopt.h>
21#include <libtransmission/utils.h>
22#include <libtransmission/variant.h>
23#include <libtransmission/version.h>
24
25#define MY_NAME "transmission-edit"
26
27static int fileCount = 0;
28static bool showVersion = false;
29static const char ** files = NULL;
30static const char * add = NULL;
31static const char * deleteme = NULL;
32static const char * replace[2] = { NULL, NULL };
33
34static tr_option options[] =
35{
36  { 'a', "add", "Add a tracker's announce URL", "a", 1, "<url>" },
37  { 'd', "delete", "Delete a tracker's announce URL", "d", 1, "<url>" },
38  { 'r', "replace", "Search and replace a substring in the announce URLs", "r", 1, "<old> <new>" },
39  { 'V', "version", "Show version number and exit", "V", 0, NULL },
40  { 0, NULL, NULL, NULL, 0, NULL }
41};
42
43static const char *
44getUsage (void)
45{
46  return "Usage: " MY_NAME " [options] torrent-file(s)";
47}
48
49static int
50parseCommandLine (int argc, const char ** argv)
51{
52  int c;
53  const char * optarg;
54
55  while ((c = tr_getopt (getUsage (), argc, argv, options, &optarg)))
56    {
57      switch (c)
58        {
59          case 'a':
60            add = optarg;
61            break;
62
63          case 'd':
64            deleteme = optarg;
65            break;
66
67          case 'r':
68            replace[0] = optarg;
69            c = tr_getopt (getUsage (), argc, argv, options, &optarg);
70            if (c != TR_OPT_UNK)
71              return 1;
72            replace[1] = optarg;
73            break;
74
75          case 'V':
76            showVersion = true;
77            break;
78
79          case TR_OPT_UNK:
80            files[fileCount++] = optarg;
81            break;
82
83          default:
84            return 1;
85        }
86    }
87
88  return 0;
89}
90
91static bool
92removeURL (tr_variant * metainfo, const char * url)
93{
94  const char * str;
95  tr_variant * announce_list;
96  bool changed = false;
97
98  if (tr_variantDictFindStr (metainfo, TR_KEY_announce, &str, NULL) && !strcmp (str, url))
99    {
100      printf ("\tRemoved \"%s\" from \"announce\"\n", str);
101      tr_variantDictRemove (metainfo, TR_KEY_announce);
102      changed = true;
103    }
104
105  if (tr_variantDictFindList (metainfo, TR_KEY_announce_list, &announce_list))
106    {
107      tr_variant * tier;
108      int tierIndex = 0;
109      while ((tier = tr_variantListChild (announce_list, tierIndex)))
110        {
111          tr_variant * node;
112          int nodeIndex = 0;
113          while ((node = tr_variantListChild (tier, nodeIndex)))
114            {
115              if (tr_variantGetStr (node, &str, NULL) && !strcmp (str, url))
116                {
117                  printf ("\tRemoved \"%s\" from \"announce-list\" tier #%d\n", str, (tierIndex+1));
118                  tr_variantListRemove (tier, nodeIndex);
119                  changed = true;
120                }
121              else ++nodeIndex;
122            }
123
124          if (tr_variantListSize (tier) == 0)
125            {
126              printf ("\tNo URLs left in tier #%d... removing tier\n", (tierIndex+1));
127              tr_variantListRemove (announce_list, tierIndex);
128            }
129          else
130            {
131              ++tierIndex;
132            }
133        }
134
135      if (tr_variantListSize (announce_list) == 0)
136        {
137          printf ("\tNo tiers left... removing announce-list\n");
138          tr_variantDictRemove (metainfo, TR_KEY_announce_list);
139        }
140    }
141
142  /* if we removed the "announce" field and there's still another track left,
143   * use it as the "announce" field */
144  if (changed && !tr_variantDictFindStr (metainfo, TR_KEY_announce, &str, NULL))
145    {
146      tr_variant * tier;
147      tr_variant * node;
148
149      if ((tier = tr_variantListChild (announce_list, 0)))
150        {
151          if ((node = tr_variantListChild (tier, 0)))
152            {
153              if (tr_variantGetStr (node, &str, NULL))
154                {
155                  tr_variantDictAddStr (metainfo, TR_KEY_announce, str);
156                  printf ("\tAdded \"%s\" to announce\n", str);
157                }
158            }
159        }
160    }
161
162  return changed;
163}
164
165static char*
166replaceSubstr (const char * str, const char * in, const char * out)
167{
168  char * walk;
169  struct evbuffer * buf = evbuffer_new ();
170  const size_t inlen = strlen (in);
171  const size_t outlen = strlen (out);
172
173  while ((walk = strstr (str, in)))
174    {
175      evbuffer_add (buf, str, walk-str);
176      evbuffer_add (buf, out, outlen);
177      str = walk + inlen;
178    }
179
180  evbuffer_add (buf, str, strlen (str));
181
182  return evbuffer_free_to_str (buf);
183}
184
185static bool
186replaceURL (tr_variant * metainfo, const char * in, const char * out)
187{
188  const char * str;
189  tr_variant * announce_list;
190  bool changed = false;
191
192  if (tr_variantDictFindStr (metainfo, TR_KEY_announce, &str, NULL) && strstr (str, in))
193    {
194      char * newstr = replaceSubstr (str, in, out);
195      printf ("\tReplaced in \"announce\": \"%s\" --> \"%s\"\n", str, newstr);
196      tr_variantDictAddStr (metainfo, TR_KEY_announce, newstr);
197      tr_free (newstr);
198      changed = true;
199    }
200
201  if (tr_variantDictFindList (metainfo, TR_KEY_announce_list, &announce_list))
202    {
203      tr_variant * tier;
204      int tierCount = 0;
205      while ((tier = tr_variantListChild (announce_list, tierCount++)))
206        {
207          tr_variant * node;
208          int nodeCount = 0;
209          while ((node = tr_variantListChild (tier, nodeCount++)))
210            {
211              if (tr_variantGetStr (node, &str, NULL) && strstr (str, in))
212                {
213                  char * newstr = replaceSubstr (str, in, out);
214                  printf ("\tReplaced in \"announce-list\" tier %d: \"%s\" --> \"%s\"\n", tierCount, str, newstr);
215                  tr_variantFree (node);
216                  tr_variantInitStr (node, newstr, -1);
217                  tr_free (newstr);
218                  changed = true;
219                }
220            }
221        }
222    }
223
224  return changed;
225}
226
227static bool
228announce_list_has_url (tr_variant * announce_list, const char * url)
229{
230  tr_variant * tier;
231  int tierCount = 0;
232  while ((tier = tr_variantListChild (announce_list, tierCount++)))
233    {
234      tr_variant * node;
235      const char * str;
236      int nodeCount = 0;
237      while ((node = tr_variantListChild (tier, nodeCount++)))
238        if (tr_variantGetStr (node, &str, NULL) && !strcmp (str, url))
239          return true;
240    }
241
242  return false;
243}
244
245static bool
246addURL (tr_variant * metainfo, const char * url)
247{
248  const char * announce = NULL;
249  tr_variant * announce_list = NULL;
250  bool changed = false;
251  const bool had_announce = tr_variantDictFindStr (metainfo, TR_KEY_announce, &announce, NULL);
252  const bool had_announce_list = tr_variantDictFindList (metainfo, TR_KEY_announce_list, &announce_list);
253
254  if (!had_announce && !had_announce_list)
255    {
256      /* this new tracker is the only one, so add it to "announce"... */
257      printf ("\tAdded \"%s\" in \"announce\"\n", url);
258      tr_variantDictAddStr (metainfo, TR_KEY_announce, url);
259      changed = true;
260    }
261  else
262    {
263      if (!had_announce_list)
264        {
265          announce_list = tr_variantDictAddList (metainfo, TR_KEY_announce_list, 2);
266
267          if (had_announce)
268            {
269              /* we're moving from an 'announce' to an 'announce-list',
270               * so copy the old announce URL to the list */
271              tr_variant * tier = tr_variantListAddList (announce_list, 1);
272              tr_variantListAddStr (tier, announce);
273              changed = true;
274            }
275        }
276
277      /* If the user-specified URL isn't in the announce list yet, add it */
278      if (!announce_list_has_url (announce_list, url))
279        {
280          tr_variant * tier = tr_variantListAddList (announce_list, 1);
281          tr_variantListAddStr (tier, url);
282          printf ("\tAdded \"%s\" to \"announce-list\" tier %zu\n", url, tr_variantListSize (announce_list));
283          changed = true;
284        }
285    }
286
287  return changed;
288}
289
290int
291main (int argc, char * argv[])
292{
293  int i;
294  int changedCount = 0;
295
296  files = tr_new0 (const char*, argc);
297
298  tr_setMessageLevel (TR_MSG_ERR);
299
300  if (parseCommandLine (argc, (const char**)argv))
301    return EXIT_FAILURE;
302
303  if (showVersion)
304    {
305      fprintf (stderr, MY_NAME" "LONG_VERSION_STRING"\n");
306      return EXIT_SUCCESS;
307    }
308
309  if (fileCount < 1)
310    {
311      fprintf (stderr, "ERROR: No torrent files specified.\n");
312      tr_getopt_usage (MY_NAME, getUsage (), options);
313      fprintf (stderr, "\n");
314      return EXIT_FAILURE;
315    }
316
317  if (!add && !deleteme && !replace[0])
318    {
319      fprintf (stderr, "ERROR: Must specify -a, -d or -r\n");
320      tr_getopt_usage (MY_NAME, getUsage (), options);
321      fprintf (stderr, "\n");
322      return EXIT_FAILURE;
323    }
324
325  for (i=0; i<fileCount; ++i)
326    {
327      tr_variant top;
328      bool changed = false;
329      const char * filename = files[i];
330
331      printf ("%s\n", filename);
332
333      if (tr_variantFromFile (&top, TR_VARIANT_FMT_BENC, filename))
334        {
335          printf ("\tError reading file\n");
336          continue;
337        }
338
339      if (deleteme != NULL)
340        changed |= removeURL (&top, deleteme);
341
342      if (add != NULL)
343        changed = addURL (&top, add);
344
345      if (replace[0] && replace[1])
346        changed |= replaceURL (&top, replace[0], replace[1]);
347
348      if (changed)
349        {
350          ++changedCount;
351          tr_variantToFile (&top, TR_VARIANT_FMT_BENC, filename);
352        }
353
354      tr_variantFree (&top);
355    }
356
357  printf ("Changed %d files\n", changedCount);
358
359  tr_free (files);
360  return EXIT_SUCCESS;
361}
Note: See TracBrowser for help on using the repository browser.