2 * This file Copyright (C) 2012-2014 Mnemosyne LLC
4 * It may be used under the GNU GPL versions 2 or 3
5 * or any future license endorsed by Mnemosyne LLC.
9 #include <stdio.h> /* fprintf() */
10 #include <string.h> /* strcmp(), strchr(), memcmp() */
11 #include <stdlib.h> /* qsort() */
14 #define CURL_DISABLE_TYPECHECK /* otherwise -Wunreachable-code goes insane */
15 #include <curl/curl.h>
17 #include <event2/buffer.h>
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>
28 #define MY_NAME "transmission-show"
29 #define TIMEOUT_SECS 30
31 static tr_option options[] =
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 }
40 static char const* getUsage(void)
42 return "Usage: " MY_NAME " [options] <.torrent file>";
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;
51 static int parseCommandLine(int argc, char const* const* argv)
56 while ((c = tr_getopt(getUsage(), argc, argv, options, &optarg)) != TR_OPT_DONE)
88 static void doShowMagnet(tr_info const* inf)
90 char* str = tr_torrentInfoGetMagnetLink(inf);
95 static int compare_files_by_name(void const* va, void const* vb)
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);
102 static char const* unix_timestamp_to_str(time_t timestamp)
109 struct tm const* const local_time = localtime(×tamp);
111 if (local_time == NULL)
116 static char buffer[32];
117 tr_strlcpy(buffer, asctime(local_time), TR_N_ELEMENTS(buffer));
119 char* const newline_pos = strchr(buffer, '\n');
121 if (newline_pos != NULL)
129 static void showInfo(tr_info const* inf)
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));
145 if (inf->comment != NULL && *inf->comment != '\0')
147 printf(" Comment: %s\n", inf->comment);
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");
159 printf("\nTRACKERS\n");
161 for (unsigned int i = 0; i < inf->trackerCount; ++i)
163 if (prevTier != inf->trackers[i].tier)
165 prevTier = inf->trackers[i].tier;
166 printf("\n Tier #%d\n", prevTier + 1);
169 printf(" %s\n", inf->trackers[i].announce);
176 if (inf->webseedCount > 0)
178 printf("\nWEBSEEDS\n\n");
180 for (unsigned int i = 0; i < inf->webseedCount; ++i)
182 printf(" %s\n", inf->webseeds[i]);
190 printf("\nFILES\n\n");
191 files = tr_new(tr_file*, inf->fileCount);
193 for (unsigned int i = 0; i < inf->fileCount; ++i)
195 files[i] = &inf->files[i];
200 qsort(files, inf->fileCount, sizeof(tr_file*), compare_files_by_name);
203 for (unsigned int i = 0; i < inf->fileCount; ++i)
205 printf(" %s (%s)\n", files[i]->name, tr_formatter_size_B(buf, files[i]->length, sizeof(buf)));
211 static size_t writeFunc(void* ptr, size_t size, size_t nmemb, void* buf)
213 size_t const byteCount = size * nmemb;
214 evbuffer_add(buf, ptr, byteCount);
218 static CURL* tr_curl_easy_init(struct evbuffer* writebuf)
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, "");
230 static void doScrape(tr_info const* inf)
232 for (unsigned int i = 0; i < inf->trackerCount; ++i)
236 struct evbuffer* buf;
237 char const* scrape = inf->trackers[i].scrape;
239 char escaped[SHA_DIGEST_LENGTH * 3 + 1];
246 tr_http_escape_sha1(escaped, inf->hash);
248 url = tr_strdup_printf("%s%cinfo_hash=%s", scrape, strchr(scrape, '?') != NULL ? '&' : '?', escaped);
250 printf("%s ... ", url);
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);
258 if ((res = curl_easy_perform(curl)) != CURLE_OK)
260 printf("error: %s\n", curl_easy_strerror(res));
265 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response);
269 printf("error: unexpected response %ld \"%s\"\n", response, tr_webGetResponseStr(response));
275 bool matched = false;
276 char const* begin = (char const*)evbuffer_pullup(buf, -1);
278 if (tr_variantFromBenc(&top, begin, evbuffer_get_length(buf)) == 0)
280 if (tr_variantDictFindDict(&top, TR_KEY_files, &files))
286 while (tr_variantDictChild(files, i++, &key, &val))
288 if (memcmp(inf->hash, tr_quark_get_string(key, NULL), SHA_DIGEST_LENGTH) == 0)
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);
300 tr_variantFree(&top);
305 printf("no match\n");
310 curl_easy_cleanup(curl);
316 int tr_main(int argc, char* argv[])
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);
327 if (parseCommandLine(argc, (char const* const*)argv) != 0)
334 fprintf(stderr, MY_NAME " " LONG_VERSION_STRING "\n");
338 /* make sure the user specified a filename */
339 if (filename == NULL)
341 fprintf(stderr, "ERROR: No .torrent file specified.\n");
342 tr_getopt_usage(MY_NAME, getUsage(), options);
343 fprintf(stderr, "\n");
347 /* try to parse the .torrent file */
348 ctor = tr_ctorNew(NULL);
349 tr_ctorSetMetainfoFromFile(ctor, filename);
350 err = tr_torrentParse(ctor, &inf);
353 if (err != TR_PARSE_OK)
355 fprintf(stderr, "Error parsing .torrent file \"%s\"\n", filename);
365 printf("Name: %s\n", inf.name);
366 printf("File: %s\n", filename);
382 tr_metainfoFree(&inf);