]> granicus.if.org Git - transmission/blob - utils/show.c
Update to Uncrustify 0.68.1
[transmission] / utils / show.c
1 /*
2  * This file Copyright (C) 2012-2014 Mnemosyne LLC
3  *
4  * It may be used under the GNU GPL versions 2 or 3
5  * or any future license endorsed by Mnemosyne LLC.
6  *
7  */
8
9 #include <stdio.h> /* fprintf() */
10 #include <string.h> /* strcmp(), strchr(), memcmp() */
11 #include <stdlib.h> /* qsort() */
12 #include <time.h>
13
14 #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
15 #include <curl/curl.h>
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/web.h> /* tr_webGetResponseStr() */
23 #include <libtransmission/variant.h>
24 #include <libtransmission/version.h>
25
26 #include "units.h"
27
28 #define MY_NAME "transmission-show"
29 #define TIMEOUT_SECS 30
30
31 static tr_option options[] =
32 {
33     { 'm', "magnet", "Give a magnet link for the specified torrent", "m", 0, NULL },
34     { 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", 0, NULL },
35     { 'u', "unsorted", "Do not sort files by name", "u", 0, NULL },
36     { 'V', "version", "Show version number and exit", "V", 0, NULL },
37     { 0, NULL, NULL, NULL, 0, NULL }
38 };
39
40 static char const* getUsage(void)
41 {
42     return "Usage: " MY_NAME " [options] <.torrent file>";
43 }
44
45 static bool magnetFlag = false;
46 static bool scrapeFlag = false;
47 static bool unsorted = false;
48 static bool showVersion = false;
49 char const* filename = NULL;
50
51 static int parseCommandLine(int argc, char const* const* argv)
52 {
53     int c;
54     char const* optarg;
55
56     while ((c = tr_getopt(getUsage(), argc, argv, options, &optarg)) != TR_OPT_DONE)
57     {
58         switch (c)
59         {
60         case 'm':
61             magnetFlag = true;
62             break;
63
64         case 's':
65             scrapeFlag = true;
66             break;
67
68         case 'u':
69             unsorted = true;
70             break;
71
72         case 'V':
73             showVersion = true;
74             break;
75
76         case TR_OPT_UNK:
77             filename = optarg;
78             break;
79
80         default:
81             return 1;
82         }
83     }
84
85     return 0;
86 }
87
88 static void doShowMagnet(tr_info const* inf)
89 {
90     char* str = tr_torrentInfoGetMagnetLink(inf);
91     printf("%s", str);
92     tr_free(str);
93 }
94
95 static int compare_files_by_name(void const* va, void const* vb)
96 {
97     tr_file const* a = *(tr_file const* const*)va;
98     tr_file const* b = *(tr_file const* const*)vb;
99     return strcmp(a->name, b->name);
100 }
101
102 static char const* unix_timestamp_to_str(time_t timestamp)
103 {
104     if (timestamp == 0)
105     {
106         return "Unknown";
107     }
108
109     struct tm const* const local_time = localtime(&timestamp);
110
111     if (local_time == NULL)
112     {
113         return "Invalid";
114     }
115
116     static char buffer[32];
117     tr_strlcpy(buffer, asctime(local_time), TR_N_ELEMENTS(buffer));
118
119     char* const newline_pos = strchr(buffer, '\n');
120
121     if (newline_pos != NULL)
122     {
123         *newline_pos = '\0';
124     }
125
126     return buffer;
127 }
128
129 static void showInfo(tr_info const* inf)
130 {
131     char buf[128];
132     tr_file** files;
133     int prevTier = -1;
134
135     /**
136     ***  General Info
137     **/
138
139     printf("GENERAL\n\n");
140     printf("  Name: %s\n", inf->name);
141     printf("  Hash: %s\n", inf->hashString);
142     printf("  Created by: %s\n", inf->creator ? inf->creator : "Unknown");
143     printf("  Created on: %s\n", unix_timestamp_to_str(inf->dateCreated));
144
145     if (inf->comment != NULL && *inf->comment != '\0')
146     {
147         printf("  Comment: %s\n", inf->comment);
148     }
149
150     printf("  Piece Count: %d\n", inf->pieceCount);
151     printf("  Piece Size: %s\n", tr_formatter_mem_B(buf, inf->pieceSize, sizeof(buf)));
152     printf("  Total Size: %s\n", tr_formatter_size_B(buf, inf->totalSize, sizeof(buf)));
153     printf("  Privacy: %s\n", inf->isPrivate ? "Private torrent" : "Public torrent");
154
155     /**
156     ***  Trackers
157     **/
158
159     printf("\nTRACKERS\n");
160
161     for (unsigned int i = 0; i < inf->trackerCount; ++i)
162     {
163         if (prevTier != inf->trackers[i].tier)
164         {
165             prevTier = inf->trackers[i].tier;
166             printf("\n  Tier #%d\n", prevTier + 1);
167         }
168
169         printf("  %s\n", inf->trackers[i].announce);
170     }
171
172     /**
173     ***
174     **/
175
176     if (inf->webseedCount > 0)
177     {
178         printf("\nWEBSEEDS\n\n");
179
180         for (unsigned int i = 0; i < inf->webseedCount; ++i)
181         {
182             printf("  %s\n", inf->webseeds[i]);
183         }
184     }
185
186     /**
187     ***  Files
188     **/
189
190     printf("\nFILES\n\n");
191     files = tr_new(tr_file*, inf->fileCount);
192
193     for (unsigned int i = 0; i < inf->fileCount; ++i)
194     {
195         files[i] = &inf->files[i];
196     }
197
198     if (!unsorted)
199     {
200         qsort(files, inf->fileCount, sizeof(tr_file*), compare_files_by_name);
201     }
202
203     for (unsigned int i = 0; i < inf->fileCount; ++i)
204     {
205         printf("  %s (%s)\n", files[i]->name, tr_formatter_size_B(buf, files[i]->length, sizeof(buf)));
206     }
207
208     tr_free(files);
209 }
210
211 static size_t writeFunc(void* ptr, size_t size, size_t nmemb, void* buf)
212 {
213     size_t const byteCount = size * nmemb;
214     evbuffer_add(buf, ptr, byteCount);
215     return byteCount;
216 }
217
218 static CURL* tr_curl_easy_init(struct evbuffer* writebuf)
219 {
220     CURL* curl = curl_easy_init();
221     curl_easy_setopt(curl, CURLOPT_USERAGENT, MY_NAME "/" LONG_VERSION_STRING);
222     curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunc);
223     curl_easy_setopt(curl, CURLOPT_WRITEDATA, writebuf);
224     curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
225     curl_easy_setopt(curl, CURLOPT_VERBOSE, tr_env_key_exists("TR_CURL_VERBOSE"));
226     curl_easy_setopt(curl, CURLOPT_ENCODING, "");
227     return curl;
228 }
229
230 static void doScrape(tr_info const* inf)
231 {
232     for (unsigned int i = 0; i < inf->trackerCount; ++i)
233     {
234         CURL* curl;
235         CURLcode res;
236         struct evbuffer* buf;
237         char const* scrape = inf->trackers[i].scrape;
238         char* url;
239         char escaped[SHA_DIGEST_LENGTH * 3 + 1];
240
241         if (scrape == NULL)
242         {
243             continue;
244         }
245
246         tr_http_escape_sha1(escaped, inf->hash);
247
248         url = tr_strdup_printf("%s%cinfo_hash=%s", scrape, strchr(scrape, '?') != NULL ? '&' : '?', escaped);
249
250         printf("%s ... ", url);
251         fflush(stdout);
252
253         buf = evbuffer_new();
254         curl = tr_curl_easy_init(buf);
255         curl_easy_setopt(curl, CURLOPT_URL, url);
256         curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT_SECS);
257
258         if ((res = curl_easy_perform(curl)) != CURLE_OK)
259         {
260             printf("error: %s\n", curl_easy_strerror(res));
261         }
262         else
263         {
264             long response;
265             curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
266
267             if (response != 200)
268             {
269                 printf("error: unexpected response %ld \"%s\"\n", response, tr_webGetResponseStr(response));
270             }
271             else /* HTTP OK */
272             {
273                 tr_variant top;
274                 tr_variant* files;
275                 bool matched = false;
276                 char const* begin = (char const*)evbuffer_pullup(buf, -1);
277
278                 if (tr_variantFromBenc(&top, begin, evbuffer_get_length(buf)) == 0)
279                 {
280                     if (tr_variantDictFindDict(&top, TR_KEY_files, &files))
281                     {
282                         int i = 0;
283                         tr_quark key;
284                         tr_variant* val;
285
286                         while (tr_variantDictChild(files, i++, &key, &val))
287                         {
288                             if (memcmp(inf->hash, tr_quark_get_string(key, NULL), SHA_DIGEST_LENGTH) == 0)
289                             {
290                                 int64_t seeders = -1;
291                                 int64_t leechers = -1;
292                                 tr_variantDictFindInt(val, TR_KEY_complete, &seeders);
293                                 tr_variantDictFindInt(val, TR_KEY_incomplete, &leechers);
294                                 printf("%d seeders, %d leechers\n", (int)seeders, (int)leechers);
295                                 matched = true;
296                             }
297                         }
298                     }
299
300                     tr_variantFree(&top);
301                 }
302
303                 if (!matched)
304                 {
305                     printf("no match\n");
306                 }
307             }
308         }
309
310         curl_easy_cleanup(curl);
311         evbuffer_free(buf);
312         tr_free(url);
313     }
314 }
315
316 int tr_main(int argc, char* argv[])
317 {
318     int err;
319     tr_info inf;
320     tr_ctor* ctor;
321
322     tr_logSetLevel(TR_LOG_ERROR);
323     tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
324     tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
325     tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
326
327     if (parseCommandLine(argc, (char const* const*)argv) != 0)
328     {
329         return EXIT_FAILURE;
330     }
331
332     if (showVersion)
333     {
334         fprintf(stderr, MY_NAME " " LONG_VERSION_STRING "\n");
335         return EXIT_SUCCESS;
336     }
337
338     /* make sure the user specified a filename */
339     if (filename == NULL)
340     {
341         fprintf(stderr, "ERROR: No .torrent file specified.\n");
342         tr_getopt_usage(MY_NAME, getUsage(), options);
343         fprintf(stderr, "\n");
344         return EXIT_FAILURE;
345     }
346
347     /* try to parse the .torrent file */
348     ctor = tr_ctorNew(NULL);
349     tr_ctorSetMetainfoFromFile(ctor, filename);
350     err = tr_torrentParse(ctor, &inf);
351     tr_ctorFree(ctor);
352
353     if (err != TR_PARSE_OK)
354     {
355         fprintf(stderr, "Error parsing .torrent file \"%s\"\n", filename);
356         return EXIT_FAILURE;
357     }
358
359     if (magnetFlag)
360     {
361         doShowMagnet(&inf);
362     }
363     else
364     {
365         printf("Name: %s\n", inf.name);
366         printf("File: %s\n", filename);
367         printf("\n");
368         fflush(stdout);
369
370         if (scrapeFlag)
371         {
372             doScrape(&inf);
373         }
374         else
375         {
376             showInfo(&inf);
377         }
378     }
379
380     /* cleanup */
381     putc('\n', stdout);
382     tr_metainfoFree(&inf);
383     return EXIT_SUCCESS;
384 }