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", false, NULL },
34 { 's', "scrape", "Ask the torrent's trackers how many peers are in the torrent's swarm", "s", false, NULL },
35 { 'u', "unsorted", "Do not sort files by name", "u", false, NULL },
36 { 'V', "version", "Show version number and exit", "V", false, NULL },
37 { 0, NULL, NULL, NULL, false, NULL }
38 };
39
getUsage(void)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
parseCommandLine(int argc,char const * const * argv)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
doShowMagnet(tr_info const * inf)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
compare_files_by_name(void const * va,void const * vb)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
unix_timestamp_to_str(time_t timestamp)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(×tamp);
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
showInfo(tr_info const * inf)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 (!tr_str_is_empty(inf->comment))
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
writeFunc(void * ptr,size_t size,size_t nmemb,void * buf)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
tr_curl_easy_init(struct evbuffer * writebuf)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
doScrape(tr_info const * inf)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;
291 if (!tr_variantDictFindInt(val, TR_KEY_complete, &seeders))
292 {
293 seeders = -1;
294 }
295
296 int64_t leechers;
297 if (!tr_variantDictFindInt(val, TR_KEY_incomplete, &leechers))
298 {
299 leechers = -1;
300 }
301
302 printf("%d seeders, %d leechers\n", (int)seeders, (int)leechers);
303 matched = true;
304 }
305
306 ++i;
307 }
308 }
309
310 tr_variantFree(&top);
311 }
312
313 if (!matched)
314 {
315 printf("no match\n");
316 }
317 }
318 }
319
320 curl_easy_cleanup(curl);
321 evbuffer_free(buf);
322 tr_free(url);
323 }
324 }
325
tr_main(int argc,char * argv[])326 int tr_main(int argc, char* argv[])
327 {
328 int err;
329 tr_info inf;
330 tr_ctor* ctor;
331
332 tr_logSetLevel(TR_LOG_ERROR);
333 tr_formatter_mem_init(MEM_K, MEM_K_STR, MEM_M_STR, MEM_G_STR, MEM_T_STR);
334 tr_formatter_size_init(DISK_K, DISK_K_STR, DISK_M_STR, DISK_G_STR, DISK_T_STR);
335 tr_formatter_speed_init(SPEED_K, SPEED_K_STR, SPEED_M_STR, SPEED_G_STR, SPEED_T_STR);
336
337 if (parseCommandLine(argc, (char const* const*)argv) != 0)
338 {
339 return EXIT_FAILURE;
340 }
341
342 if (showVersion)
343 {
344 fprintf(stderr, MY_NAME " " LONG_VERSION_STRING "\n");
345 return EXIT_SUCCESS;
346 }
347
348 /* make sure the user specified a filename */
349 if (filename == NULL)
350 {
351 fprintf(stderr, "ERROR: No .torrent file specified.\n");
352 tr_getopt_usage(MY_NAME, getUsage(), options);
353 fprintf(stderr, "\n");
354 return EXIT_FAILURE;
355 }
356
357 /* try to parse the .torrent file */
358 ctor = tr_ctorNew(NULL);
359 tr_ctorSetMetainfoFromFile(ctor, filename);
360 err = tr_torrentParse(ctor, &inf);
361 tr_ctorFree(ctor);
362
363 if (err != TR_PARSE_OK)
364 {
365 fprintf(stderr, "Error parsing .torrent file \"%s\"\n", filename);
366 return EXIT_FAILURE;
367 }
368
369 if (magnetFlag)
370 {
371 doShowMagnet(&inf);
372 }
373 else
374 {
375 printf("Name: %s\n", inf.name);
376 printf("File: %s\n", filename);
377 printf("\n");
378 fflush(stdout);
379
380 if (scrapeFlag)
381 {
382 doScrape(&inf);
383 }
384 else
385 {
386 showInfo(&inf);
387 }
388 }
389
390 /* cleanup */
391 putc('\n', stdout);
392 tr_metainfoFree(&inf);
393 return EXIT_SUCCESS;
394 }
395