1 /*
2 
3 Copyright (c) 2003-2017, Arvid Norberg
4 All rights reserved.
5 
6 Redistribution and use in source and binary forms, with or without
7 modification, are permitted provided that the following conditions
8 are met:
9 
10     * Redistributions of source code must retain the above copyright
11       notice, this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright
13       notice, this list of conditions and the following disclaimer in
14       the documentation and/or other materials provided with the distribution.
15     * Neither the name of the author nor the names of its
16       contributors may be used to endorse or promote products derived
17       from this software without specific prior written permission.
18 
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
30 
31 */
32 
33 #include <cstdio> // for snprintf
34 #include <cstdlib> // for atoi
35 #include <cstring>
36 #include <utility>
37 #include <deque>
38 #include <fstream>
39 #include <regex>
40 #include <algorithm> // for min()/max()
41 
42 #include "libtorrent/config.hpp"
43 
44 #ifdef TORRENT_WINDOWS
45 #include <direct.h> // for _mkdir and _getcwd
46 #include <sys/types.h> // for _stat
47 #include <sys/stat.h>
48 #endif
49 
50 #ifdef TORRENT_UTP_LOG_ENABLE
51 #include "libtorrent/utp_stream.hpp"
52 #endif
53 
54 #include "libtorrent/torrent_info.hpp"
55 #include "libtorrent/announce_entry.hpp"
56 #include "libtorrent/entry.hpp"
57 #include "libtorrent/bencode.hpp"
58 #include "libtorrent/session.hpp"
59 #include "libtorrent/identify_client.hpp"
60 #include "libtorrent/alert_types.hpp"
61 #include "libtorrent/ip_filter.hpp"
62 #include "libtorrent/magnet_uri.hpp"
63 #include "libtorrent/peer_info.hpp"
64 #include "libtorrent/bdecode.hpp"
65 #include "libtorrent/add_torrent_params.hpp"
66 #include "libtorrent/time.hpp"
67 #include "libtorrent/read_resume_data.hpp"
68 #include "libtorrent/write_resume_data.hpp"
69 #include "libtorrent/string_view.hpp"
70 #include "libtorrent/disk_interface.hpp" // for open_file_state
71 
72 #include "torrent_view.hpp"
73 #include "session_view.hpp"
74 #include "print.hpp"
75 
76 using lt::total_milliseconds;
77 using lt::alert;
78 using lt::piece_index_t;
79 using lt::file_index_t;
80 using lt::torrent_handle;
81 using lt::add_torrent_params;
82 using lt::cache_status;
83 using lt::total_seconds;
84 using lt::torrent_flags_t;
85 using lt::seconds;
86 using lt::operator "" _sv;
87 using lt::address_v4;
88 using lt::address_v6;
89 
90 using std::chrono::duration_cast;
91 using std::stoi;
92 
93 #ifdef _WIN32
94 
95 #include <windows.h>
96 #include <conio.h>
97 
sleep_and_input(int * c,lt::time_duration const sleep)98 bool sleep_and_input(int* c, lt::time_duration const sleep)
99 {
100 	for (int i = 0; i < 2; ++i)
101 	{
102 		if (_kbhit())
103 		{
104 			*c = _getch();
105 			return true;
106 		}
107 		std::this_thread::sleep_for(sleep / 2);
108 	}
109 	return false;
110 }
111 
112 #else
113 
114 #include <termios.h>
115 #include <sys/ioctl.h>
116 #include <csignal>
117 #include <utility>
118 #include <dirent.h>
119 
120 struct set_keypress
121 {
122 	enum terminal_mode {
123 		echo = 1,
124 		canonical = 2
125 	};
126 
set_keypressset_keypress127 	explicit set_keypress(std::uint8_t const mode = 0)
128 	{
129 		termios new_settings;
130 		tcgetattr(0, &stored_settings);
131 		new_settings = stored_settings;
132 		// Disable canonical mode, and set buffer size to 1 byte
133 		// and disable echo
134 		if (mode & echo) new_settings.c_lflag |= ECHO;
135 		else new_settings.c_lflag &= ~ECHO;
136 
137 		if (mode & canonical) new_settings.c_lflag |= ICANON;
138 		else new_settings.c_lflag &= ~ICANON;
139 
140 		new_settings.c_cc[VTIME] = 0;
141 		new_settings.c_cc[VMIN] = 1;
142 		tcsetattr(0,TCSANOW,&new_settings);
143 	}
~set_keypressset_keypress144 	~set_keypress() { tcsetattr(0, TCSANOW, &stored_settings); }
145 private:
146 	termios stored_settings;
147 };
148 
sleep_and_input(int * c,lt::time_duration const sleep)149 bool sleep_and_input(int* c, lt::time_duration const sleep)
150 {
151 	lt::time_point const done = lt::clock_type::now() + sleep;
152 	int ret = 0;
153 retry:
154 	fd_set set;
155 	FD_ZERO(&set);
156 	FD_SET(0, &set);
157 	int const delay = total_milliseconds(done - lt::clock_type::now());
158 	timeval tv = {delay / 1000, (delay % 1000) * 1000 };
159 	ret = select(1, &set, nullptr, nullptr, &tv);
160 	if (ret > 0)
161 	{
162 		*c = getc(stdin);
163 		return true;
164 	}
165 	if (errno == EINTR)
166 	{
167 		if (lt::clock_type::now() < done)
168 			goto retry;
169 		return false;
170 	}
171 
172 	if (ret < 0 && errno != 0 && errno != ETIMEDOUT)
173 	{
174 		std::fprintf(stderr, "select failed: %s\n", strerror(errno));
175 		std::this_thread::sleep_for(lt::milliseconds(500));
176 	}
177 
178 	return false;
179 }
180 
181 #endif
182 
183 bool print_trackers = false;
184 bool print_peers = false;
185 bool print_connecting_peers = false;
186 bool print_log = false;
187 bool print_downloads = false;
188 bool print_matrix = false;
189 bool print_file_progress = false;
190 bool show_pad_files = false;
191 bool show_dht_status = false;
192 bool sequential_download = false;
193 
194 bool print_ip = true;
195 bool print_local_ip = false;
196 bool print_timers = false;
197 bool print_block = false;
198 bool print_fails = false;
199 bool print_send_bufs = true;
200 bool print_disk_stats = false;
201 
202 // the number of times we've asked to save resume data
203 // without having received a response (successful or failure)
204 int num_outstanding_resume_data = 0;
205 
206 #ifndef TORRENT_DISABLE_DHT
207 std::vector<lt::dht_lookup> dht_active_requests;
208 std::vector<lt::dht_routing_bucket> dht_routing_table;
209 #endif
210 
to_hex(lt::sha1_hash const & s)211 std::string to_hex(lt::sha1_hash const& s)
212 {
213 	std::stringstream ret;
214 	ret << s;
215 	return ret.str();
216 }
217 
load_file(std::string const & filename,std::vector<char> & v,int limit=8000000)218 bool load_file(std::string const& filename, std::vector<char>& v
219 	, int limit = 8000000)
220 {
221 	std::fstream f(filename, std::ios_base::in | std::ios_base::binary);
222 	f.seekg(0, std::ios_base::end);
223 	auto const s = f.tellg();
224 	if (s > limit || s < 0) return false;
225 	f.seekg(0, std::ios_base::beg);
226 	v.resize(static_cast<std::size_t>(s));
227 	if (s == std::fstream::pos_type(0)) return !f.fail();
228 	f.read(v.data(), v.size());
229 	return !f.fail();
230 }
231 
is_absolute_path(std::string const & f)232 bool is_absolute_path(std::string const& f)
233 {
234 	if (f.empty()) return false;
235 #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
236 	int i = 0;
237 	// match the xx:\ or xx:/ form
238 	while (f[i] && strchr("abcdefghijklmnopqrstuvxyz", f[i])) ++i;
239 	if (i < int(f.size()-1) && f[i] == ':' && (f[i+1] == '\\' || f[i+1] == '/'))
240 		return true;
241 
242 	// match the \\ form
243 	if (int(f.size()) >= 2 && f[0] == '\\' && f[1] == '\\')
244 		return true;
245 	return false;
246 #else
247 	if (f[0] == '/') return true;
248 	return false;
249 #endif
250 }
251 
path_append(std::string const & lhs,std::string const & rhs)252 std::string path_append(std::string const& lhs, std::string const& rhs)
253 {
254 	if (lhs.empty() || lhs == ".") return rhs;
255 	if (rhs.empty() || rhs == ".") return lhs;
256 
257 #if defined(TORRENT_WINDOWS) || defined(TORRENT_OS2)
258 #define TORRENT_SEPARATOR "\\"
259 	bool need_sep = lhs[lhs.size()-1] != '\\' && lhs[lhs.size()-1] != '/';
260 #else
261 #define TORRENT_SEPARATOR "/"
262 	bool need_sep = lhs[lhs.size()-1] != '/';
263 #endif
264 	return lhs + (need_sep?TORRENT_SEPARATOR:"") + rhs;
265 }
266 
make_absolute_path(std::string const & p)267 std::string make_absolute_path(std::string const& p)
268 {
269 	if (is_absolute_path(p)) return p;
270 	std::string ret;
271 #if defined TORRENT_WINDOWS
272 	char* cwd = ::_getcwd(nullptr, 0);
273 	ret = path_append(cwd, p);
274 	std::free(cwd);
275 #else
276 	char* cwd = ::getcwd(nullptr, 0);
277 	ret = path_append(cwd, p);
278 	std::free(cwd);
279 #endif
280 	return ret;
281 }
282 
print_endpoint(lt::tcp::endpoint const & ep)283 std::string print_endpoint(lt::tcp::endpoint const& ep)
284 {
285 	using namespace lt;
286 	lt::error_code ec;
287 	char buf[200];
288 	address const& addr = ep.address();
289 	if (addr.is_v6())
290 		std::snprintf(buf, sizeof(buf), "[%s]:%d", addr.to_string(ec).c_str(), ep.port());
291 	else
292 		std::snprintf(buf, sizeof(buf), "%s:%d", addr.to_string(ec).c_str(), ep.port());
293 	return buf;
294 }
295 
296 using lt::torrent_status;
297 
298 FILE* g_log_file = nullptr;
299 
peer_index(lt::tcp::endpoint addr,std::vector<lt::peer_info> const & peers)300 int peer_index(lt::tcp::endpoint addr, std::vector<lt::peer_info> const& peers)
301 {
302 	using namespace lt;
303 	auto i = std::find_if(peers.begin(), peers.end()
304 		, [&addr](peer_info const& pi) { return pi.ip == addr; });
305 	if (i == peers.end()) return -1;
306 
307 	return int(i - peers.begin());
308 }
309 
310 // returns the number of lines printed
print_peer_info(std::string & out,std::vector<lt::peer_info> const & peers,int max_lines)311 int print_peer_info(std::string& out
312 	, std::vector<lt::peer_info> const& peers, int max_lines)
313 {
314 	using namespace lt;
315 	int pos = 0;
316 	if (print_ip) out += "IP                             ";
317 	if (print_local_ip) out += "local IP                       ";
318 	out += "progress        down     (total | peak   )  up      (total | peak   ) sent-req tmo bsy rcv flags          dn  up  source  ";
319 	if (print_fails) out += "fail hshf ";
320 	if (print_send_bufs) out += "rq sndb (recvb |alloc | wmrk ) q-bytes ";
321 	if (print_timers) out += "inactive wait timeout q-time ";
322 	out += "  v disk ^    rtt  ";
323 	if (print_block) out += "block-progress ";
324 	out += "client \x1b[K\n";
325 	++pos;
326 
327 	char str[500];
328 	for (std::vector<peer_info>::const_iterator i = peers.begin();
329 		i != peers.end(); ++i)
330 	{
331 		if ((i->flags & (peer_info::handshake | peer_info::connecting)
332 			&& !print_connecting_peers))
333 		{
334 			continue;
335 		}
336 
337 		if (print_ip)
338 		{
339 			std::snprintf(str, sizeof(str), "%-30s ", (::print_endpoint(i->ip) +
340 				(i->flags & peer_info::utp_socket ? " [uTP]" : "") +
341 				(i->flags & peer_info::i2p_socket ? " [i2p]" : "")
342 				).c_str());
343 			out += str;
344 		}
345 		if (print_local_ip)
346 		{
347 			std::snprintf(str, sizeof(str), "%-30s ", ::print_endpoint(i->local_endpoint).c_str());
348 			out += str;
349 		}
350 
351 		char temp[10];
352 		std::snprintf(temp, sizeof(temp), "%d/%d"
353 			, i->download_queue_length
354 			, i->target_dl_queue_length);
355 		temp[7] = 0;
356 
357 		char peer_progress[10];
358 		std::snprintf(peer_progress, sizeof(peer_progress), "%.1f%%", i->progress_ppm / 10000.f);
359 		std::snprintf(str, sizeof(str)
360 			, "%s %s%s (%s|%s) %s%s (%s|%s) %s%7s %4d%4d%4d %s%s%s%s%s%s%s%s%s%s%s%s%s%s %s%s%s %s%s%s %s%s%s%s%s%s "
361 			, progress_bar(i->progress_ppm / 1000, 15, col_green, '#', '-', peer_progress).c_str()
362 			, esc("32"), add_suffix(i->down_speed, "/s").c_str()
363 			, add_suffix(i->total_download).c_str(), add_suffix(i->download_rate_peak, "/s").c_str()
364 			, esc("31"), add_suffix(i->up_speed, "/s").c_str(), add_suffix(i->total_upload).c_str()
365 			, add_suffix(i->upload_rate_peak, "/s").c_str(), esc("0")
366 
367 			, temp // sent requests and target number of outstanding reqs.
368 			, i->timed_out_requests
369 			, i->busy_requests
370 			, i->upload_queue_length
371 
372 			, color("I", (i->flags & peer_info::interesting)?col_white:col_blue).c_str()
373 			, color("C", (i->flags & peer_info::choked)?col_white:col_blue).c_str()
374 			, color("i", (i->flags & peer_info::remote_interested)?col_white:col_blue).c_str()
375 			, color("c", (i->flags & peer_info::remote_choked)?col_white:col_blue).c_str()
376 			, color("x", (i->flags & peer_info::supports_extensions)?col_white:col_blue).c_str()
377 			, color("o", (i->flags & peer_info::local_connection)?col_white:col_blue).c_str()
378 			, color("p", (i->flags & peer_info::on_parole)?col_white:col_blue).c_str()
379 			, color("O", (i->flags & peer_info::optimistic_unchoke)?col_white:col_blue).c_str()
380 			, color("S", (i->flags & peer_info::snubbed)?col_white:col_blue).c_str()
381 			, color("U", (i->flags & peer_info::upload_only)?col_white:col_blue).c_str()
382 			, color("e", (i->flags & peer_info::endgame_mode)?col_white:col_blue).c_str()
383 			, color("E", (i->flags & peer_info::rc4_encrypted)?col_white:(i->flags & peer_info::plaintext_encrypted)?col_cyan:col_blue).c_str()
384 			, color("h", (i->flags & peer_info::holepunched)?col_white:col_blue).c_str()
385 			, color("s", (i->flags & peer_info::seed)?col_white:col_blue).c_str()
386 
387 			, color("d", (i->read_state & peer_info::bw_disk)?col_white:col_blue).c_str()
388 			, color("l", (i->read_state & peer_info::bw_limit)?col_white:col_blue).c_str()
389 			, color("n", (i->read_state & peer_info::bw_network)?col_white:col_blue).c_str()
390 			, color("d", (i->write_state & peer_info::bw_disk)?col_white:col_blue).c_str()
391 			, color("l", (i->write_state & peer_info::bw_limit)?col_white:col_blue).c_str()
392 			, color("n", (i->write_state & peer_info::bw_network)?col_white:col_blue).c_str()
393 
394 			, color("t", (i->source & peer_info::tracker)?col_white:col_blue).c_str()
395 			, color("p", (i->source & peer_info::pex)?col_white:col_blue).c_str()
396 			, color("d", (i->source & peer_info::dht)?col_white:col_blue).c_str()
397 			, color("l", (i->source & peer_info::lsd)?col_white:col_blue).c_str()
398 			, color("r", (i->source & peer_info::resume_data)?col_white:col_blue).c_str()
399 			, color("i", (i->source & peer_info::incoming)?col_white:col_blue).c_str());
400 		out += str;
401 
402 		if (print_fails)
403 		{
404 			std::snprintf(str, sizeof(str), "%4d %4d "
405 				, i->failcount, i->num_hashfails);
406 			out += str;
407 		}
408 		if (print_send_bufs)
409 		{
410 			std::snprintf(str, sizeof(str), "%2d %6d %6d|%6d|%6d%5dkB "
411 				, i->requests_in_buffer, i->used_send_buffer
412 				, i->used_receive_buffer
413 				, i->receive_buffer_size
414 				, i->receive_buffer_watermark
415 				, i->queue_bytes / 1000);
416 			out += str;
417 		}
418 		if (print_timers)
419 		{
420 			char req_timeout[20] = "-";
421 			// timeout is only meaningful if there is at least one outstanding
422 			// request to the peer
423 			if (i->download_queue_length > 0)
424 				std::snprintf(req_timeout, sizeof(req_timeout), "%d", i->request_timeout);
425 
426 			std::snprintf(str, sizeof(str), "%8d %4d %7s %6d "
427 				, int(total_seconds(i->last_active))
428 				, int(total_seconds(i->last_request))
429 				, req_timeout
430 				, int(total_seconds(i->download_queue_time)));
431 			out += str;
432 		}
433 		std::snprintf(str, sizeof(str), "%s|%s %5d "
434 			, add_suffix(i->pending_disk_bytes).c_str()
435 			, add_suffix(i->pending_disk_read_bytes).c_str()
436 			, i->rtt);
437 		out += str;
438 
439 		if (print_block)
440 		{
441 			if (i->downloading_piece_index >= piece_index_t(0))
442 			{
443 				char buf[50];
444 				std::snprintf(buf, sizeof(buf), "%d:%d"
445 					, static_cast<int>(i->downloading_piece_index), i->downloading_block_index);
446 				out += progress_bar(
447 					i->downloading_progress * 1000 / i->downloading_total, 14, col_green, '-', '#', buf);
448 			}
449 			else
450 			{
451 				out += progress_bar(0, 14);
452 			}
453 		}
454 
455 		out += " ";
456 
457 		if (i->flags & lt::peer_info::handshake)
458 		{
459 			out += esc("31");
460 			out += " waiting for handshake";
461 			out += esc("0");
462 		}
463 		else if (i->flags & lt::peer_info::connecting)
464 		{
465 			out += esc("31");
466 			out += " connecting to peer";
467 			out += esc("0");
468 		}
469 		else
470 		{
471 			out += " ";
472 			out += i->client;
473 		}
474 		out += "\x1b[K\n";
475 		++pos;
476 		if (pos >= max_lines) break;
477 	}
478 	return pos;
479 }
480 
481 lt::storage_mode_t allocation_mode = lt::storage_mode_sparse;
482 std::string save_path(".");
483 int torrent_upload_limit = 0;
484 int torrent_download_limit = 0;
485 std::string monitor_dir;
486 int poll_interval = 5;
487 int max_connections_per_torrent = 50;
488 bool seed_mode = false;
489 bool stats_enabled = false;
490 int cache_size = -1;
491 
492 bool share_mode = false;
493 bool disable_storage = false;
494 
495 bool quit = false;
496 
signal_handler(int)497 void signal_handler(int)
498 {
499 	// make the main loop terminate
500 	quit = true;
501 }
502 
503 // if non-empty, a peer that will be added to all torrents
504 std::string peer;
505 
print_settings(int const start,int const num,char const * const fmt)506 void print_settings(int const start, int const num
507 	, char const* const fmt)
508 {
509 	for (int i = start; i < start + num; ++i)
510 	{
511 		char const* name = lt::name_for_setting(i);
512 		if (!name || name[0] == '\0') continue;
513 		std::printf(fmt, name);
514 	}
515 }
516 
assign_setting(lt::settings_pack & settings,std::string const & key,char const * value)517 void assign_setting(lt::settings_pack& settings, std::string const& key, char const* value)
518 {
519 	int const sett_name = lt::setting_by_name(key);
520 	if (sett_name < 0)
521 	{
522 		std::fprintf(stderr, "unknown setting: \"%s\"\n", key.c_str());
523 		std::exit(1);
524 	}
525 
526 	using lt::settings_pack;
527 
528 	switch (sett_name & settings_pack::type_mask)
529 	{
530 		case settings_pack::string_type_base:
531 			settings.set_str(sett_name, value);
532 			break;
533 		case settings_pack::bool_type_base:
534 			if (value == "1"_sv || value == "on"_sv || value == "true"_sv)
535 			{
536 				settings.set_bool(sett_name, true);
537 			}
538 			else if (value == "0"_sv || value == "off"_sv || value == "false"_sv)
539 			{
540 				settings.set_bool(sett_name, false);
541 			}
542 			else
543 			{
544 				std::fprintf(stderr, "invalid value for \"%s\". expected 0 or 1\n"
545 					, key.c_str());
546 				std::exit(1);
547 			}
548 			break;
549 		case settings_pack::int_type_base:
550 			using namespace libtorrent::literals;
551 			static std::map<lt::string_view, int> const enums = {
552 				{"no_piece_suggestions"_sv, settings_pack::no_piece_suggestions},
553 				{"suggest_read_cache"_sv, settings_pack::suggest_read_cache},
554 				{"fixed_slots_choker"_sv, settings_pack::fixed_slots_choker},
555 				{"rate_based_choker"_sv, settings_pack::rate_based_choker},
556 				{"round_robin"_sv, settings_pack::round_robin},
557 				{"fastest_upload"_sv, settings_pack::fastest_upload},
558 				{"anti_leech"_sv, settings_pack::anti_leech},
559 				{"enable_os_cache"_sv, settings_pack::enable_os_cache},
560 				{"disable_os_cache"_sv, settings_pack::disable_os_cache},
561 				{"prefer_tcp"_sv, settings_pack::prefer_tcp},
562 				{"peer_proportional"_sv, settings_pack::peer_proportional},
563 				{"pe_forced"_sv, settings_pack::pe_forced},
564 				{"pe_enabled"_sv, settings_pack::pe_enabled},
565 				{"pe_disabled"_sv, settings_pack::pe_disabled},
566 				{"pe_plaintext"_sv, settings_pack::pe_plaintext},
567 				{"pe_rc4"_sv, settings_pack::pe_rc4},
568 				{"pe_both"_sv, settings_pack::pe_both},
569 				{"none"_sv, settings_pack::none},
570 				{"socks4"_sv, settings_pack::socks4},
571 				{"socks5"_sv, settings_pack::socks5},
572 				{"socks5_pw"_sv, settings_pack::socks5_pw},
573 				{"http"_sv, settings_pack::http},
574 				{"http_pw"_sv, settings_pack::http_pw},
575 				{"i2p_proxy"_sv, settings_pack::i2p_proxy},
576 			};
577 
578 			auto const it = enums.find(lt::string_view(value));
579 			if (it != enums.end())
580 			{
581 				settings.set_int(sett_name, it->second);
582 				break;
583 			}
584 
585 			static std::map<lt::string_view, lt::alert_category_t> const alert_categories = {
586 				{"error"_sv, lt::alert_category::error},
587 				{"peer"_sv, lt::alert_category::peer},
588 				{"port_mapping"_sv, lt::alert_category::port_mapping},
589 				{"storage"_sv, lt::alert_category::storage},
590 				{"tracker"_sv, lt::alert_category::tracker},
591 				{"connect"_sv, lt::alert_category::connect},
592 				{"status"_sv, lt::alert_category::status},
593 				{"ip_block"_sv, lt::alert_category::ip_block},
594 				{"performance_warning"_sv, lt::alert_category::performance_warning},
595 				{"dht"_sv, lt::alert_category::dht},
596 				{"session_log"_sv, lt::alert_category::session_log},
597 				{"torrent_log"_sv, lt::alert_category::torrent_log},
598 				{"peer_log"_sv, lt::alert_category::peer_log},
599 				{"incoming_request"_sv, lt::alert_category::incoming_request},
600 				{"dht_log"_sv, lt::alert_category::dht_log},
601 				{"dht_operation"_sv, lt::alert_category::dht_operation},
602 				{"port_mapping_log"_sv, lt::alert_category::port_mapping_log},
603 				{"picker_log"_sv, lt::alert_category::picker_log},
604 				{"file_progress"_sv, lt::alert_category::file_progress},
605 				{"piece_progress"_sv, lt::alert_category::piece_progress},
606 				{"upload"_sv, lt::alert_category::upload},
607 				{"block_progress"_sv, lt::alert_category::block_progress},
608 				{"all"_sv, lt::alert_category::all},
609 			};
610 
611 			std::stringstream flags(value);
612 			std::string f;
613 			lt::alert_category_t val;
614 			while (std::getline(flags, f, ',')) try
615 			{
616 				auto const it = alert_categories.find(f);
617 				if (it == alert_categories.end())
618 					val |= lt::alert_category_t{unsigned(std::stoi(f))};
619 				else
620 					val |= it->second;
621 			}
622 			catch (std::invalid_argument const&)
623 			{
624 				std::fprintf(stderr, "invalid value for \"%s\". expected integer or enum value\n"
625 					, key.c_str());
626 				std::exit(1);
627 			}
628 
629 			settings.set_int(sett_name, val);
630 			break;
631 	}
632 }
633 
resume_file(lt::sha1_hash const & info_hash)634 std::string resume_file(lt::sha1_hash const& info_hash)
635 {
636 	return path_append(save_path, path_append(".resume"
637 		, to_hex(info_hash) + ".resume"));
638 }
639 
set_torrent_params(lt::add_torrent_params & p)640 void set_torrent_params(lt::add_torrent_params& p)
641 {
642 	p.max_connections = max_connections_per_torrent;
643 	p.max_uploads = -1;
644 	p.upload_limit = torrent_upload_limit;
645 	p.download_limit = torrent_download_limit;
646 
647 	if (seed_mode) p.flags |= lt::torrent_flags::seed_mode;
648 	if (disable_storage) p.storage = lt::disabled_storage_constructor;
649 	if (share_mode) p.flags |= lt::torrent_flags::share_mode;
650 	p.save_path = save_path;
651 	p.storage_mode = allocation_mode;
652 }
653 
add_magnet(lt::session & ses,lt::string_view uri)654 void add_magnet(lt::session& ses, lt::string_view uri)
655 {
656 	lt::error_code ec;
657 	lt::add_torrent_params p = lt::parse_magnet_uri(uri.to_string(), ec);
658 
659 	if (ec)
660 	{
661 		std::printf("invalid magnet link \"%s\": %s\n"
662 			, uri.to_string().c_str(), ec.message().c_str());
663 		return;
664 	}
665 
666 	std::vector<char> resume_data;
667 	if (load_file(resume_file(p.info_hash), resume_data))
668 	{
669 		p = lt::read_resume_data(resume_data, ec);
670 		if (ec) std::printf("  failed to load resume data: %s\n", ec.message().c_str());
671 	}
672 
673 	set_torrent_params(p);
674 
675 	std::printf("adding magnet: %s\n", uri.to_string().c_str());
676 	ses.async_add_torrent(std::move(p));
677 }
678 
679 // return false on failure
add_torrent(lt::session & ses,std::string torrent)680 bool add_torrent(lt::session& ses, std::string torrent)
681 {
682 	using lt::add_torrent_params;
683 	using lt::storage_mode_t;
684 
685 	static int counter = 0;
686 
687 	std::printf("[%d] %s\n", counter++, torrent.c_str());
688 
689 	lt::error_code ec;
690 	auto ti = std::make_shared<lt::torrent_info>(torrent, ec);
691 	if (ec)
692 	{
693 		std::printf("failed to load torrent \"%s\": %s\n"
694 			, torrent.c_str(), ec.message().c_str());
695 		return false;
696 	}
697 
698 	add_torrent_params p;
699 
700 	std::vector<char> resume_data;
701 	if (load_file(resume_file(ti->info_hash()), resume_data))
702 	{
703 		p = lt::read_resume_data(resume_data, ec);
704 		if (ec) std::printf("  failed to load resume data: %s\n", ec.message().c_str());
705 	}
706 
707 	set_torrent_params(p);
708 
709 	p.ti = ti;
710 	p.flags &= ~lt::torrent_flags::duplicate_is_error;
711 	ses.async_add_torrent(std::move(p));
712 	return true;
713 }
714 
list_dir(std::string path,bool (* filter_fun)(lt::string_view),lt::error_code & ec)715 std::vector<std::string> list_dir(std::string path
716 	, bool (*filter_fun)(lt::string_view)
717 	, lt::error_code& ec)
718 {
719 	std::vector<std::string> ret;
720 #ifdef TORRENT_WINDOWS
721 	if (!path.empty() && path[path.size()-1] != '\\') path += "\\*";
722 	else path += "*";
723 
724 	WIN32_FIND_DATAA fd;
725 	HANDLE handle = FindFirstFileA(path.c_str(), &fd);
726 	if (handle == INVALID_HANDLE_VALUE)
727 	{
728 		ec.assign(GetLastError(), boost::system::system_category());
729 		return ret;
730 	}
731 
732 	do
733 	{
734 		lt::string_view p = fd.cFileName;
735 		if (filter_fun(p))
736 			ret.push_back(p.to_string());
737 
738 	} while (FindNextFileA(handle, &fd));
739 	FindClose(handle);
740 #else
741 
742 	if (!path.empty() && path[path.size()-1] == '/')
743 		path.resize(path.size()-1);
744 
745 	DIR* handle = opendir(path.c_str());
746 	if (handle == nullptr)
747 	{
748 		ec.assign(errno, boost::system::system_category());
749 		return ret;
750 	}
751 
752 	struct dirent* de;
753 	while ((de = readdir(handle)))
754 	{
755 		lt::string_view p(de->d_name);
756 		if (filter_fun(p))
757 			ret.push_back(p.to_string());
758 	}
759 	closedir(handle);
760 #endif
761 	return ret;
762 }
763 
scan_dir(std::string const & dir_path,lt::session & ses)764 void scan_dir(std::string const& dir_path, lt::session& ses)
765 {
766 	using namespace lt;
767 
768 	error_code ec;
769 	std::vector<std::string> ents = list_dir(dir_path
770 		, [](lt::string_view p) { return p.size() > 8 && p.substr(p.size() - 8) == ".torrent"; }, ec);
771 	if (ec)
772 	{
773 		std::fprintf(stderr, "failed to list directory: (%s : %d) %s\n"
774 			, ec.category().name(), ec.value(), ec.message().c_str());
775 		return;
776 	}
777 
778 	for (auto const& e : ents)
779 	{
780 		std::string const file = path_append(dir_path, e);
781 
782 		// there's a new file in the monitor directory, load it up
783 		if (add_torrent(ses, file))
784 		{
785 			if (::remove(file.c_str()) < 0)
786 			{
787 				std::fprintf(stderr, "failed to remove torrent file: \"%s\"\n"
788 					, file.c_str());
789 			}
790 		}
791 	}
792 }
793 
timestamp()794 char const* timestamp()
795 {
796 	time_t t = std::time(nullptr);
797 	tm* timeinfo = std::localtime(&t);
798 	static char str[200];
799 	std::strftime(str, 200, "%b %d %X", timeinfo);
800 	return str;
801 }
802 
print_alert(lt::alert const * a,std::string & str)803 void print_alert(lt::alert const* a, std::string& str)
804 {
805 	using namespace lt;
806 
807 	if (a->category() & alert_category::error)
808 	{
809 		str += esc("31");
810 	}
811 	else if (a->category() & (alert_category::peer | alert_category::storage))
812 	{
813 		str += esc("33");
814 	}
815 	str += "[";
816 	str += timestamp();
817 	str += "] ";
818 	str += a->message();
819 	str += esc("0");
820 
821 	if (g_log_file)
822 		std::fprintf(g_log_file, "[%s] %s\n", timestamp(),  a->message().c_str());
823 }
824 
save_file(std::string const & filename,std::vector<char> const & v)825 int save_file(std::string const& filename, std::vector<char> const& v)
826 {
827 	std::fstream f(filename, std::ios_base::trunc | std::ios_base::out | std::ios_base::binary);
828 	f.write(v.data(), v.size());
829 	return !f.fail();
830 }
831 
832 // returns true if the alert was handled (and should not be printed to the log)
833 // returns false if the alert was not handled
handle_alert(torrent_view & view,session_view & ses_view,lt::session &,lt::alert * a)834 bool handle_alert(torrent_view& view, session_view& ses_view
835 	, lt::session&, lt::alert* a)
836 {
837 	using namespace lt;
838 
839 	if (session_stats_alert* s = alert_cast<session_stats_alert>(a))
840 	{
841 		ses_view.update_counters(s->counters()
842 			, duration_cast<microseconds>(s->timestamp().time_since_epoch()).count());
843 		return !stats_enabled;
844 	}
845 
846 #ifndef TORRENT_DISABLE_DHT
847 	if (dht_stats_alert* p = alert_cast<dht_stats_alert>(a))
848 	{
849 		dht_active_requests = p->active_requests;
850 		dht_routing_table = p->routing_table;
851 		return true;
852 	}
853 #endif
854 
855 #ifdef TORRENT_USE_OPENSSL
856 	if (torrent_need_cert_alert* p = alert_cast<torrent_need_cert_alert>(a))
857 	{
858 		torrent_handle h = p->handle;
859 		std::string base_name = path_append("certificates", to_hex(h.info_hash()));
860 		std::string cert = base_name + ".pem";
861 		std::string priv = base_name + "_key.pem";
862 
863 #ifdef TORRENT_WINDOWS
864 		struct ::_stat st;
865 		int ret = ::_stat(cert.c_str(), &st);
866 		if (ret < 0 || (st.st_mode & _S_IFREG) == 0)
867 #else
868 		struct ::stat st;
869 		int ret = ::stat(cert.c_str(), &st);
870 		if (ret < 0 || (st.st_mode & S_IFREG) == 0)
871 #endif
872 		{
873 			char msg[256];
874 			std::snprintf(msg, sizeof(msg), "ERROR. could not load certificate %s: %s\n"
875 				, cert.c_str(), std::strerror(errno));
876 			if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg);
877 			return true;
878 		}
879 
880 #ifdef TORRENT_WINDOWS
881 		ret = ::_stat(priv.c_str(), &st);
882 		if (ret < 0 || (st.st_mode & _S_IFREG) == 0)
883 #else
884 		ret = ::stat(priv.c_str(), &st);
885 		if (ret < 0 || (st.st_mode & S_IFREG) == 0)
886 #endif
887 		{
888 			char msg[256];
889 			std::snprintf(msg, sizeof(msg), "ERROR. could not load private key %s: %s\n"
890 				, priv.c_str(), std::strerror(errno));
891 			if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg);
892 			return true;
893 		}
894 
895 		char msg[256];
896 		std::snprintf(msg, sizeof(msg), "loaded certificate %s and key %s\n", cert.c_str(), priv.c_str());
897 		if (g_log_file) std::fprintf(g_log_file, "[%s] %s\n", timestamp(), msg);
898 
899 		h.set_ssl_certificate(cert, priv, "certificates/dhparams.pem", "1234");
900 		h.resume();
901 	}
902 #endif
903 
904 	// don't log every peer we try to connect to
905 	if (alert_cast<peer_connect_alert>(a)) return true;
906 
907 	if (peer_disconnected_alert* pd = alert_cast<peer_disconnected_alert>(a))
908 	{
909 		// ignore failures to connect and peers not responding with a
910 		// handshake. The peers that we successfully connect to and then
911 		// disconnect is more interesting.
912 		if (pd->op == operation_t::connect
913 			|| pd->error == errors::timed_out_no_handshake)
914 			return true;
915 	}
916 
917 #ifdef _MSC_VER
918 // it seems msvc makes the definitions of 'p' escape the if-statement here
919 #pragma warning(push)
920 #pragma warning(disable: 4456)
921 #endif
922 
923 	if (metadata_received_alert* p = alert_cast<metadata_received_alert>(a))
924 	{
925 		torrent_handle h = p->handle;
926 		h.save_resume_data(torrent_handle::save_info_dict);
927 		++num_outstanding_resume_data;
928 	}
929 	else if (add_torrent_alert* p = alert_cast<add_torrent_alert>(a))
930 	{
931 		if (p->error)
932 		{
933 			std::fprintf(stderr, "failed to add torrent: %s %s\n"
934 				, p->params.ti ? p->params.ti->name().c_str() : p->params.name.c_str()
935 				, p->error.message().c_str());
936 		}
937 		else
938 		{
939 			torrent_handle h = p->handle;
940 
941 			h.save_resume_data(torrent_handle::save_info_dict | torrent_handle::only_if_modified);
942 			++num_outstanding_resume_data;
943 
944 			// if we have a peer specified, connect to it
945 			if (!peer.empty())
946 			{
947 				char* port = (char*) strrchr((char*)peer.c_str(), ':');
948 				if (port != nullptr)
949 				{
950 					*port++ = 0;
951 					char const* ip = peer.c_str();
952 					int peer_port = atoi(port);
953 					error_code ec;
954 					if (peer_port > 0)
955 						h.connect_peer(tcp::endpoint(address::from_string(ip, ec), std::uint16_t(peer_port)));
956 				}
957 			}
958 		}
959 	}
960 	else if (torrent_finished_alert* p = alert_cast<torrent_finished_alert>(a))
961 	{
962 		p->handle.set_max_connections(max_connections_per_torrent / 2);
963 
964 		// write resume data for the finished torrent
965 		// the alert handler for save_resume_data_alert
966 		// will save it to disk
967 		torrent_handle h = p->handle;
968 		h.save_resume_data(torrent_handle::save_info_dict);
969 		++num_outstanding_resume_data;
970 	}
971 	else if (save_resume_data_alert* p = alert_cast<save_resume_data_alert>(a))
972 	{
973 		--num_outstanding_resume_data;
974 		auto const buf = write_resume_data_buf(p->params);
975 		save_file(resume_file(p->params.info_hash), buf);
976 	}
977 	else if (save_resume_data_failed_alert* p = alert_cast<save_resume_data_failed_alert>(a))
978 	{
979 		--num_outstanding_resume_data;
980 		// don't print the error if it was just that we didn't need to save resume
981 		// data. Returning true means "handled" and not printed to the log
982 		return p->error == lt::errors::resume_data_not_modified;
983 	}
984 	else if (torrent_paused_alert* p = alert_cast<torrent_paused_alert>(a))
985 	{
986 		// write resume data for the finished torrent
987 		// the alert handler for save_resume_data_alert
988 		// will save it to disk
989 		torrent_handle h = p->handle;
990 		h.save_resume_data(torrent_handle::save_info_dict);
991 		++num_outstanding_resume_data;
992 	}
993 	else if (state_update_alert* p = alert_cast<state_update_alert>(a))
994 	{
995 		view.update_torrents(std::move(p->status));
996 		return true;
997 	}
998 	else if (torrent_removed_alert* p = alert_cast<torrent_removed_alert>(a))
999 	{
1000 		view.remove_torrent(std::move(p->handle));
1001 	}
1002 	return false;
1003 
1004 #ifdef _MSC_VER
1005 #pragma warning(pop)
1006 #endif
1007 
1008 }
1009 
pop_alerts(torrent_view & view,session_view & ses_view,lt::session & ses,std::deque<std::string> & events)1010 void pop_alerts(torrent_view& view, session_view& ses_view
1011 	, lt::session& ses, std::deque<std::string>& events)
1012 {
1013 	std::vector<lt::alert*> alerts;
1014 	ses.pop_alerts(&alerts);
1015 	for (auto a : alerts)
1016 	{
1017 		if (::handle_alert(view, ses_view, ses, a)) continue;
1018 
1019 		// if we didn't handle the alert, print it to the log
1020 		std::string event_string;
1021 		print_alert(a, event_string);
1022 		events.push_back(event_string);
1023 		if (events.size() >= 20) events.pop_front();
1024 	}
1025 }
1026 
print_piece(lt::partial_piece_info const * pp,lt::cached_piece_info const * cs,std::vector<lt::peer_info> const & peers,std::string & out)1027 void print_piece(lt::partial_piece_info const* pp
1028 	, lt::cached_piece_info const* cs
1029 	, std::vector<lt::peer_info> const& peers
1030 	, std::string& out)
1031 {
1032 	using namespace lt;
1033 
1034 	char str[1024];
1035 	assert(pp == nullptr || cs == nullptr || cs->piece == pp->piece_index);
1036 	int piece = static_cast<int>(pp ? pp->piece_index : cs->piece);
1037 	int num_blocks = pp ? pp->blocks_in_piece : int(cs->blocks.size());
1038 
1039 	std::snprintf(str, sizeof(str), "%5d:[", piece);
1040 	out += str;
1041 	string_view last_color;
1042 	for (int j = 0; j < num_blocks; ++j)
1043 	{
1044 		int const index = pp ? peer_index(pp->blocks[j].peer(), peers) % 36 : -1;
1045 		char const* chr = " ";
1046 		bool const snubbed = index >= 0 ? bool(peers[index].flags & lt::peer_info::snubbed) : false;
1047 
1048 		char const* color = "";
1049 
1050 		if (pp == nullptr)
1051 		{
1052 			color = cs->blocks[j] ? esc("34;7") : esc("0");
1053 			chr = " ";
1054 		}
1055 		else
1056 		{
1057 			if (cs && cs->blocks[j] && pp->blocks[j].state != block_info::finished)
1058 				color = esc("36;7");
1059 			else if (pp->blocks[j].bytes_progress > 0
1060 					&& pp->blocks[j].state == block_info::requested)
1061 			{
1062 				if (pp->blocks[j].num_peers > 1) color = esc("0;1");
1063 				else color = snubbed ? esc("0;35") : esc("0;33");
1064 
1065 #ifndef TORRENT_WINDOWS
1066 				static char const* const progress[] = {
1067 					"\u2581", "\u2582", "\u2583", "\u2584",
1068 					"\u2585", "\u2586", "\u2587", "\u2588"
1069 				};
1070 				chr = progress[pp->blocks[j].bytes_progress * 8 / pp->blocks[j].block_size];
1071 #else
1072 				static char const* const progress[] = { "\xb0", "\xb1", "\xb2" };
1073 				chr = progress[pp->blocks[j].bytes_progress * 3 / pp->blocks[j].block_size];
1074 #endif
1075 			}
1076 			else if (pp->blocks[j].state == block_info::finished) color = esc("32;7");
1077 			else if (pp->blocks[j].state == block_info::writing) color = esc("36;7");
1078 			else if (pp->blocks[j].state == block_info::requested)
1079 			{
1080 				color = snubbed ? esc("0;35") : esc("0");
1081 				chr = "=";
1082 			}
1083 			else { color = esc("0"); chr = " "; }
1084 		}
1085 		if (last_color != color)
1086 		{
1087 			out += color;
1088 			last_color = color;
1089 		}
1090 		out += chr;
1091 	}
1092 	out += esc("0");
1093 	out += "]";
1094 }
1095 
is_resume_file(std::string const & s)1096 bool is_resume_file(std::string const& s)
1097 {
1098 	static std::string const hex_digit = "0123456789abcdef";
1099 	if (s.size() != 40 + 7) return false;
1100 	if (s.substr(40) != ".resume") return false;
1101 	for (char const c : s.substr(0, 40))
1102 	{
1103 		if (hex_digit.find(c) == std::string::npos) return false;
1104 	}
1105 	return true;
1106 }
1107 
main(int argc,char * argv[])1108 int main(int argc, char* argv[])
1109 {
1110 #ifndef _WIN32
1111 	// sets the terminal to single-character mode
1112 	// and resets when destructed
1113 	set_keypress s;
1114 #endif
1115 
1116 	if (argc == 1)
1117 	{
1118 		std::fprintf(stderr, R"(usage: client_test [OPTIONS] [TORRENT|MAGNETURL]
1119 OPTIONS:
1120 
1121 CLIENT OPTIONS
1122   -f <log file>         logs all events to the given file
1123   -s <path>             sets the save path for downloads. This also determines
1124                         the resume data save directory. Torrents from the resume
1125                         directory are automatically added to the session on
1126                         startup.
1127   -m <path>             sets the .torrent monitor directory. torrent files
1128                         dropped in the directory are added the session and the
1129                         resume data directory, and removed from the monitor dir.
1130   -t <seconds>          sets the scan interval of the monitor dir
1131   -F <milliseconds>     sets the UI refresh rate. This is the number of
1132                         milliseconds between screen refreshes.
1133   -k                    enable high performance settings. This overwrites any other
1134                         previous command line options, so be sure to specify this first
1135   -G                    Add torrents in seed-mode (i.e. assume all pieces
1136                         are present and check hashes on-demand)
1137   -e <loops>            exit client after the specified number of iterations
1138                         through the main loop
1139   -O                    print session stats counters to the log)"
1140 #ifdef TORRENT_UTP_LOG_ENABLE
1141 R"(
1142   -q                    Enable uTP transport-level verbose logging
1143 )"
1144 #endif
1145 R"(
1146 LIBTORRENT SETTINGS
1147   --<name-of-setting>=<value>
1148                         set the libtorrent setting <name> to <value>
1149   --list-settings       print all libtorrent settings and exit
1150 
1151 BITTORRENT OPTIONS
1152   -T <limit>            sets the max number of connections per torrent
1153   -U <rate>             sets per-torrent upload rate
1154   -D <rate>             sets per-torrent download rate
1155   -Q                    enables share mode. Share mode attempts to maximize
1156                         share ratio rather than downloading
1157   -r <IP:port>          connect to specified peer
1158 
1159 NETWORK OPTIONS
1160   -x <file>             loads an emule IP-filter file
1161   -Y                    Rate limit local peers)"
1162 #if TORRENT_USE_I2P
1163 R"(  -i <i2p-host>         the hostname to an I2P SAM bridge to use
1164 )"
1165 #endif
1166 R"(
1167 DISK OPTIONS
1168   -a <mode>             sets the allocation mode. [sparse|allocate]
1169   -0                    disable disk I/O, read garbage and don't flush to disk
1170 
1171 TORRENT is a path to a .torrent file
1172 MAGNETURL is a magnet link
1173 
1174 alert mask flags:
1175 	error peer port_mapping storage tracker connect status ip_block
1176 	performance_warning dht session_log torrent_log peer_log incoming_request
1177 	dht_log dht_operation port_mapping_log picker_log file_progress piece_progress
1178 	upload block_progress all
1179 
1180 examples:
1181   --alert_mask=error,port_mapping,tracker,connect,session_log
1182   --alert_mask=error,session_log,torrent_log,peer_log
1183   --alert_mask=error,dht,dht_log,dht_operation
1184   --alert_mask=all
1185 )") ;
1186 		return 0;
1187 	}
1188 
1189 	using lt::settings_pack;
1190 	using lt::session_handle;
1191 
1192 	torrent_view view;
1193 	session_view ses_view;
1194 
1195 	lt::session_params params;
1196 
1197 #ifndef TORRENT_DISABLE_DHT
1198 	params.dht_settings.privacy_lookups = true;
1199 
1200 	std::vector<char> in;
1201 	if (load_file(".ses_state", in))
1202 	{
1203 		lt::error_code ec;
1204 		lt::bdecode_node e = lt::bdecode(in, ec);
1205 		if (!ec) params = read_session_params(e, session_handle::save_dht_state);
1206 	}
1207 #endif
1208 
1209 	auto& settings = params.settings;
1210 	settings.set_int(settings_pack::cache_size, cache_size);
1211 	settings.set_int(settings_pack::choking_algorithm, settings_pack::rate_based_choker);
1212 
1213 	settings.set_str(settings_pack::user_agent, "client_test/" LIBTORRENT_VERSION);
1214 	settings.set_int(settings_pack::alert_mask
1215 		, lt::alert_category::error
1216 		| lt::alert_category::peer
1217 		| lt::alert_category::port_mapping
1218 		| lt::alert_category::storage
1219 		| lt::alert_category::tracker
1220 		| lt::alert_category::connect
1221 		| lt::alert_category::status
1222 		| lt::alert_category::ip_block
1223 		| lt::alert_category::performance_warning
1224 		| lt::alert_category::dht
1225 		| lt::alert_category::incoming_request
1226 		| lt::alert_category::dht_operation
1227 		| lt::alert_category::port_mapping_log
1228 		| lt::alert_category::file_progress);
1229 
1230 	lt::time_duration refresh_delay = lt::milliseconds(500);
1231 	bool rate_limit_locals = false;
1232 
1233 	std::deque<std::string> events;
1234 	int loop_limit = -1;
1235 
1236 	lt::time_point next_dir_scan = lt::clock_type::now();
1237 
1238 	// load the torrents given on the commandline
1239 	std::vector<lt::string_view> torrents;
1240 	lt::ip_filter loaded_ip_filter;
1241 
1242 	for (int i = 1; i < argc; ++i)
1243 	{
1244 		if (argv[i][0] != '-')
1245 		{
1246 			torrents.push_back(argv[i]);
1247 			continue;
1248 		}
1249 
1250 		if (argv[i] == "--list-settings"_sv)
1251 		{
1252 			// print all libtorrent settings and exit
1253 			print_settings(settings_pack::string_type_base
1254 				, settings_pack::num_string_settings
1255 				, "%s=<string>\n");
1256 			print_settings(settings_pack::bool_type_base
1257 				, settings_pack::num_bool_settings
1258 				, "%s=<bool>\n");
1259 			print_settings(settings_pack::int_type_base
1260 				, settings_pack::num_int_settings
1261 				, "%s=<int>\n");
1262 			return 0;
1263 		}
1264 
1265 		// maybe this is an assignment of a libtorrent setting
1266 		if (argv[i][1] == '-' && strchr(argv[i], '=') != nullptr)
1267 		{
1268 			char const* equal = strchr(argv[i], '=');
1269 			char const* start = argv[i]+2;
1270 			// +2 is to skip the --
1271 			std::string const key(start, equal - start);
1272 			char const* value = equal + 1;
1273 
1274 			assign_setting(settings, key, value);
1275 			continue;
1276 		}
1277 
1278 		// if there's a flag but no argument following, ignore it
1279 		if (argc == i) continue;
1280 		char const* arg = argv[i+1];
1281 		if (arg == nullptr) arg = "";
1282 
1283 		switch (argv[i][1])
1284 		{
1285 			case 'f': g_log_file = std::fopen(arg, "w+"); break;
1286 			case 'k': settings = lt::high_performance_seed(); --i; break;
1287 			case 'G': seed_mode = true; --i; break;
1288 			case 's': save_path = make_absolute_path(arg); break;
1289 			case 'O': stats_enabled = true; --i; break;
1290 #ifdef TORRENT_UTP_LOG_ENABLE
1291 			case 'q':
1292 				lt::set_utp_stream_logging(true);
1293 				break;
1294 #endif
1295 			case 'U': torrent_upload_limit = atoi(arg) * 1000; break;
1296 			case 'D': torrent_download_limit = atoi(arg) * 1000; break;
1297 			case 'm': monitor_dir = make_absolute_path(arg); break;
1298 			case 'Q': share_mode = true; --i; break;
1299 			case 't': poll_interval = atoi(arg); break;
1300 			case 'F': refresh_delay = lt::milliseconds(atoi(arg)); break;
1301 			case 'a': allocation_mode = (arg == std::string("sparse"))
1302 				? lt::storage_mode_sparse
1303 				: lt::storage_mode_allocate;
1304 				break;
1305 			case 'x':
1306 				{
1307 					std::fstream filter(arg, std::ios_base::in);
1308 					if (!filter.fail())
1309 					{
1310 						std::regex regex(R"(^\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\s*-\s*([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)\s+([0-9]+)$)");
1311 
1312 						std::string line;
1313 						while (std::getline(filter, line))
1314 						{
1315 							std::smatch m;
1316 							if (std::regex_match(line, m, regex))
1317 							{
1318 								address_v4 start((stoi(m[1]) << 24) | (stoi(m[2]) << 16) | (stoi(m[3]) << 8) | stoi(m[4]));
1319 								address_v4 last((stoi(m[5]) << 24) | (stoi(m[6]) << 16) | (stoi(m[7]) << 8) | stoi(m[8]));
1320 								loaded_ip_filter.add_rule(start, last
1321 									, stoi(m[9]) <= 127 ? lt::ip_filter::blocked : 0);
1322 							}
1323 						}
1324 					}
1325 				}
1326 				break;
1327 			case 'T': max_connections_per_torrent = atoi(arg); break;
1328 			case 'r': peer = arg; break;
1329 			case 'Y':
1330 				{
1331 					--i;
1332 					rate_limit_locals = true;
1333 					break;
1334 				}
1335 			case '0': disable_storage = true; --i; break;
1336 			case 'e':
1337 				{
1338 					loop_limit = atoi(arg);
1339 					break;
1340 				}
1341 		}
1342 		++i; // skip the argument
1343 	}
1344 
1345 	// create directory for resume files
1346 #ifdef TORRENT_WINDOWS
1347 	int mkdir_ret = _mkdir(path_append(save_path, ".resume").c_str());
1348 #else
1349 	int mkdir_ret = mkdir(path_append(save_path, ".resume").c_str(), 0777);
1350 #endif
1351 	if (mkdir_ret < 0 && errno != EEXIST)
1352 	{
1353 		std::fprintf(stderr, "failed to create resume file directory: (%d) %s\n"
1354 			, errno, strerror(errno));
1355 	}
1356 
1357 	lt::session ses(std::move(params));
1358 
1359 	if (rate_limit_locals)
1360 	{
1361 		lt::ip_filter pcf;
1362 		pcf.add_rule(address_v4::from_string("0.0.0.0")
1363 			, address_v4::from_string("255.255.255.255")
1364 			, 1 << static_cast<std::uint32_t>(lt::session::global_peer_class_id));
1365 		pcf.add_rule(address_v6::from_string("::")
1366 			, address_v6::from_string("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"), 1);
1367 		ses.set_peer_class_filter(pcf);
1368 	}
1369 
1370 	ses.set_ip_filter(loaded_ip_filter);
1371 
1372 	for (auto const& i : torrents)
1373 	{
1374 		if (i.substr(0, 7) == "magnet:") add_magnet(ses, i);
1375 		else add_torrent(ses, i.to_string());
1376 	}
1377 
1378 	std::thread resume_data_loader([&ses]
1379 	{
1380 		// load resume files
1381 		lt::error_code ec;
1382 		std::string const resume_dir = path_append(save_path, ".resume");
1383 		std::vector<std::string> ents = list_dir(resume_dir
1384 			, [](lt::string_view p) { return p.size() > 7 && p.substr(p.size() - 7) == ".resume"; }, ec);
1385 		if (ec)
1386 		{
1387 			std::fprintf(stderr, "failed to list resume directory \"%s\": (%s : %d) %s\n"
1388 				, resume_dir.c_str(), ec.category().name(), ec.value(), ec.message().c_str());
1389 		}
1390 		else
1391 		{
1392 			for (auto const& e : ents)
1393 			{
1394 				// only load resume files of the form <info-hash>.resume
1395 				if (!is_resume_file(e)) continue;
1396 				std::string const file = path_append(resume_dir, e);
1397 
1398 				std::vector<char> resume_data;
1399 				if (!load_file(file, resume_data))
1400 				{
1401 					std::printf("  failed to load resume file \"%s\": %s\n"
1402 						, file.c_str(), ec.message().c_str());
1403 					continue;
1404 				}
1405 				add_torrent_params p = lt::read_resume_data(resume_data, ec);
1406 				if (ec)
1407 				{
1408 					std::printf("  failed to parse resume data \"%s\": %s\n"
1409 						, file.c_str(), ec.message().c_str());
1410 					continue;
1411 				}
1412 
1413 				ses.async_add_torrent(std::move(p));
1414 			}
1415 		}
1416 	});
1417 
1418 	// main loop
1419 	std::vector<lt::peer_info> peers;
1420 	std::vector<lt::partial_piece_info> queue;
1421 
1422 #ifndef _WIN32
1423 	signal(SIGTERM, signal_handler);
1424 	signal(SIGINT, signal_handler);
1425 #endif
1426 
1427 	while (!quit && loop_limit != 0)
1428 	{
1429 		if (loop_limit > 0) --loop_limit;
1430 
1431 		ses.post_torrent_updates();
1432 		ses.post_session_stats();
1433 		ses.post_dht_stats();
1434 
1435 		int terminal_width = 80;
1436 		int terminal_height = 50;
1437 		std::tie(terminal_width, terminal_height) = terminal_size();
1438 
1439 		// the ratio of torrent-list and details below depend on the number of
1440 		// torrents we have in the session
1441 		int const height = std::min(terminal_height / 2
1442 			, std::max(5, view.num_visible_torrents() + 2));
1443 		view.set_size(terminal_width, height);
1444 		ses_view.set_pos(height);
1445 		ses_view.set_width(terminal_width);
1446 
1447 		int c = 0;
1448 		if (sleep_and_input(&c, refresh_delay))
1449 		{
1450 
1451 #ifdef _WIN32
1452 			constexpr int escape_seq = 224;
1453 			constexpr int left_arrow = 75;
1454 			constexpr int right_arrow = 77;
1455 			constexpr int up_arrow = 72;
1456 			constexpr int down_arrow = 80;
1457 #else
1458 			constexpr int escape_seq = 27;
1459 			constexpr int left_arrow = 68;
1460 			constexpr int right_arrow = 67;
1461 			constexpr int up_arrow = 65;
1462 			constexpr int down_arrow = 66;
1463 #endif
1464 
1465 			torrent_handle h = view.get_active_handle();
1466 
1467 			if (c == EOF)
1468 			{
1469 				quit = true;
1470 				break;
1471 			}
1472 			do
1473 			{
1474 				if (c == escape_seq)
1475 				{
1476 					// escape code, read another character
1477 #ifdef _WIN32
1478 					int c2 = _getch();
1479 #else
1480 					int c2 = getc(stdin);
1481 					if (c2 == EOF)
1482 					{
1483 						quit = true;
1484 						break;
1485 					}
1486 					if (c2 != '[') continue;
1487 					c2 = getc(stdin);
1488 #endif
1489 					if (c2 == EOF)
1490 					{
1491 						quit = true;
1492 						break;
1493 					}
1494 					if (c2 == left_arrow)
1495 					{
1496 						int const filter = view.filter();
1497 						if (filter > 0)
1498 						{
1499 							view.set_filter(filter - 1);
1500 							h = view.get_active_handle();
1501 						}
1502 					}
1503 					else if (c2 == right_arrow)
1504 					{
1505 						int const filter = view.filter();
1506 						if (filter < torrent_view::torrents_max - 1)
1507 						{
1508 							view.set_filter(filter + 1);
1509 							h = view.get_active_handle();
1510 						}
1511 					}
1512 					else if (c2 == up_arrow)
1513 					{
1514 						view.arrow_up();
1515 						h = view.get_active_handle();
1516 					}
1517 					else if (c2 == down_arrow)
1518 					{
1519 						view.arrow_down();
1520 						h = view.get_active_handle();
1521 					}
1522 				}
1523 
1524 				if (c == ' ')
1525 				{
1526 					if (ses.is_paused()) ses.resume();
1527 					else ses.pause();
1528 				}
1529 
1530 				if (c == '[' && h.is_valid())
1531 				{
1532 					h.queue_position_up();
1533 				}
1534 
1535 				if (c == ']' && h.is_valid())
1536 				{
1537 					h.queue_position_down();
1538 				}
1539 
1540 				// add magnet link
1541 				if (c == 'm')
1542 				{
1543 					char url[4096];
1544 					url[0] = '\0';
1545 					puts("Enter magnet link:\n");
1546 
1547 #ifndef _WIN32
1548 					// enable terminal echo temporarily
1549 					set_keypress s(set_keypress::echo | set_keypress::canonical);
1550 #endif
1551 					if (std::scanf("%4095s", url) == 1) add_magnet(ses, url);
1552 					else std::printf("failed to read magnet link\n");
1553 				}
1554 
1555 				if (c == 'q')
1556 				{
1557 					quit = true;
1558 					break;
1559 				}
1560 
1561 				if (c == 'W' && h.is_valid())
1562 				{
1563 					std::set<std::string> seeds = h.url_seeds();
1564 					for (std::set<std::string>::iterator i = seeds.begin()
1565 						, end(seeds.end()); i != end; ++i)
1566 					{
1567 						h.remove_url_seed(*i);
1568 					}
1569 
1570 					seeds = h.http_seeds();
1571 					for (std::set<std::string>::iterator i = seeds.begin()
1572 						, end(seeds.end()); i != end; ++i)
1573 					{
1574 						h.remove_http_seed(*i);
1575 					}
1576 				}
1577 
1578 				if (c == 'D' && h.is_valid())
1579 				{
1580 					torrent_status const& st = view.get_active_torrent();
1581 					std::printf("\n\nARE YOU SURE YOU WANT TO DELETE THE FILES FOR '%s'. THIS OPERATION CANNOT BE UNDONE. (y/N)"
1582 						, st.name.c_str());
1583 #ifndef _WIN32
1584 					// enable terminal echo temporarily
1585 					set_keypress s(set_keypress::echo | set_keypress::canonical);
1586 #endif
1587 					char response = 'n';
1588 					int scan_ret = std::scanf("%c", &response);
1589 					if (scan_ret == 1 && response == 'y')
1590 					{
1591 						// also delete the resume file
1592 						std::string const rpath = resume_file(st.info_hash);
1593 						if (::remove(rpath.c_str()) < 0)
1594 							std::printf("failed to delete resume file (\"%s\")\n"
1595 								, rpath.c_str());
1596 
1597 						if (st.handle.is_valid())
1598 						{
1599 							ses.remove_torrent(st.handle, lt::session::delete_files);
1600 						}
1601 						else
1602 						{
1603 							std::printf("failed to delete torrent, invalid handle: %s\n"
1604 								, st.name.c_str());
1605 						}
1606 					}
1607 				}
1608 
1609 				if (c == 'j' && h.is_valid())
1610 				{
1611 					h.force_recheck();
1612 				}
1613 
1614 				if (c == 'r' && h.is_valid())
1615 				{
1616 					h.force_reannounce();
1617 				}
1618 
1619 				if (c == 's' && h.is_valid())
1620 				{
1621 					torrent_status const& ts = view.get_active_torrent();
1622 					h.set_flags(~ts.flags, lt::torrent_flags::sequential_download);
1623 				}
1624 
1625 				if (c == 'R')
1626 				{
1627 					// save resume data for all torrents
1628 					std::vector<torrent_status> const torr = ses.get_torrent_status(
1629 						[](torrent_status const& st)
1630 						{ return st.need_save_resume; }, {});
1631 					for (torrent_status const& st : torr)
1632 					{
1633 						st.handle.save_resume_data(torrent_handle::save_info_dict);
1634 						++num_outstanding_resume_data;
1635 					}
1636 				}
1637 
1638 				if (c == 'o' && h.is_valid())
1639 				{
1640 					torrent_status const& ts = view.get_active_torrent();
1641 					int num_pieces = ts.num_pieces;
1642 					if (num_pieces > 300) num_pieces = 300;
1643 					for (piece_index_t i(0); i < piece_index_t(num_pieces); ++i)
1644 					{
1645 						h.set_piece_deadline(i, (static_cast<int>(i)+5) * 1000
1646 							, torrent_handle::alert_when_available);
1647 					}
1648 				}
1649 
1650 				if (c == 'v' && h.is_valid())
1651 				{
1652 					h.scrape_tracker();
1653 				}
1654 
1655 				if (c == 'p' && h.is_valid())
1656 				{
1657 					torrent_status const& ts = view.get_active_torrent();
1658 					if ((ts.flags & (lt::torrent_flags::auto_managed
1659 						| lt::torrent_flags::paused)) == lt::torrent_flags::paused)
1660 					{
1661 						h.set_flags(lt::torrent_flags::auto_managed);
1662 					}
1663 					else
1664 					{
1665 						h.unset_flags(lt::torrent_flags::auto_managed);
1666 						h.pause(torrent_handle::graceful_pause);
1667 					}
1668 				}
1669 
1670 				// toggle force-start
1671 				if (c == 'k' && h.is_valid())
1672 				{
1673 					torrent_status const& ts = view.get_active_torrent();
1674 					h.set_flags(
1675 						~(ts.flags & lt::torrent_flags::auto_managed),
1676 						lt::torrent_flags::auto_managed);
1677 					if ((ts.flags & lt::torrent_flags::auto_managed)
1678 						&& (ts.flags & lt::torrent_flags::paused))
1679 					{
1680 						h.resume();
1681 					}
1682 				}
1683 
1684 				if (c == 'c' && h.is_valid())
1685 				{
1686 					h.clear_error();
1687 				}
1688 
1689 				// toggle displays
1690 				if (c == 't') print_trackers = !print_trackers;
1691 				if (c == 'i') print_peers = !print_peers;
1692 				if (c == 'l') print_log = !print_log;
1693 				if (c == 'd') print_downloads = !print_downloads;
1694 				if (c == 'y') print_matrix = !print_matrix;
1695 				if (c == 'f') print_file_progress = !print_file_progress;
1696 				if (c == 'P') show_pad_files = !show_pad_files;
1697 				if (c == 'g') show_dht_status = !show_dht_status;
1698 				if (c == 'x') print_disk_stats = !print_disk_stats;
1699 				// toggle columns
1700 				if (c == '1') print_ip = !print_ip;
1701 				if (c == '2') print_connecting_peers = !print_connecting_peers;
1702 				if (c == '3') print_timers = !print_timers;
1703 				if (c == '4') print_block = !print_block;
1704 				if (c == '6') print_fails = !print_fails;
1705 				if (c == '7') print_send_bufs = !print_send_bufs;
1706 				if (c == '8') print_local_ip = !print_local_ip;
1707 				if (c == 'C')
1708 				{
1709 					cache_size = (cache_size == 0) ? -1 : 0;
1710 					settings_pack p;
1711 					p.set_int(settings_pack::cache_size, cache_size);
1712 					ses.apply_settings(std::move(p));
1713 				}
1714 				if (c == 'h')
1715 				{
1716 					clear_screen();
1717 					set_cursor_pos(0,0);
1718 					print(
1719 R"(HELP SCREEN (press any key to dismiss)
1720 
1721 CLIENT OPTIONS
1722 
1723 [q] quit client                                 [m] add magnet link
1724 
1725 TORRENT ACTIONS
1726 [p] pause/resume selected torrent               [C] toggle disk cache
1727 [s] toggle sequential download                  [j] force recheck
1728 [space] toggle session pause                    [c] clear error
1729 [v] scrape                                      [D] delete torrent and data
1730 [r] force reannounce                            [R] save resume data for all torrents
1731 [o] set piece deadlines (sequential dl)         [P] toggle auto-managed
1732 [k] toggle force-started                        [W] remove all web seeds
1733  [  move queue position closer to beginning
1734  ]  move queue position closer to end
1735 
1736 DISPLAY OPTIONS
1737 left/right arrow keys: select torrent filter
1738 up/down arrow keys: select torrent
1739 [i] toggle show peers                           [d] toggle show downloading pieces
1740 [P] show pad files (in file list)               [f] toggle show files
1741 [g] show DHT                                    [x] toggle disk cache stats
1742 [t] show trackers                               [l] toggle show log
1743 [y] toggle show piece matrix
1744 
1745 COLUMN OPTIONS
1746 [1] toggle IP column                            [2] toggle show peer connection attempts
1747 [3] toggle timers column                        [4] toggle block progress column
1748                                                 [6] toggle failures column
1749 [7] toggle send buffers column                  [8] toggle local IP column
1750 )");
1751 					int tmp;
1752 					while (sleep_and_input(&tmp, lt::milliseconds(500)) == false);
1753 				}
1754 
1755 			} while (sleep_and_input(&c, lt::milliseconds(0)));
1756 			if (c == 'q')
1757 			{
1758 				quit = true;
1759 				break;
1760 			}
1761 		}
1762 
1763 		pop_alerts(view, ses_view, ses, events);
1764 
1765 		std::string out;
1766 
1767 		char str[500];
1768 
1769 		int pos = view.height() + ses_view.height();
1770 		set_cursor_pos(0, pos);
1771 
1772 		int cache_flags = print_downloads ? 0 : lt::session::disk_cache_no_pieces;
1773 		torrent_handle h = view.get_active_handle();
1774 
1775 		cache_status cs;
1776 		ses.get_cache_info(&cs, h, cache_flags);
1777 
1778 #ifndef TORRENT_DISABLE_DHT
1779 		if (show_dht_status)
1780 		{
1781 			// TODO: 3 expose these counters as performance counters
1782 /*
1783 			std::snprintf(str, sizeof(str), "DHT nodes: %d DHT cached nodes: %d "
1784 				"total DHT size: %" PRId64 " total observers: %d\n"
1785 				, sess_stat.dht_nodes, sess_stat.dht_node_cache, sess_stat.dht_global_nodes
1786 				, sess_stat.dht_total_allocations);
1787 			out += str;
1788 */
1789 
1790 			int bucket = 0;
1791 			for (lt::dht_routing_bucket const& n : dht_routing_table)
1792 			{
1793 				char const* progress_bar =
1794 					"################################"
1795 					"################################"
1796 					"################################"
1797 					"################################";
1798 				char const* short_progress_bar = "--------";
1799 				std::snprintf(str, sizeof(str)
1800 					, "%3d [%3d, %d] %s%s\x1b[K\n"
1801 					, bucket, n.num_nodes, n.num_replacements
1802 					, progress_bar + (128 - n.num_nodes)
1803 					, short_progress_bar + (8 - std::min(8, n.num_replacements)));
1804 				out += str;
1805 				pos += 1;
1806 				++bucket;
1807 			}
1808 
1809 			for (lt::dht_lookup const& l : dht_active_requests)
1810 			{
1811 				std::snprintf(str, sizeof(str)
1812 					, "  %10s target: %s "
1813 					"[limit: %2d] "
1814 					"in-flight: %-2d "
1815 					"left: %-3d "
1816 					"1st-timeout: %-2d "
1817 					"timeouts: %-2d "
1818 					"responses: %-2d "
1819 					"last_sent: %-2d "
1820 					"\x1b[K\n"
1821 					, l.type
1822 					, to_hex(l.target).c_str()
1823 					, l.branch_factor
1824 					, l.outstanding_requests
1825 					, l.nodes_left
1826 					, l.first_timeout
1827 					, l.timeouts
1828 					, l.responses
1829 					, l.last_sent);
1830 				out += str;
1831 				pos += 1;
1832 			}
1833 		}
1834 #endif
1835 		lt::time_point const now = lt::clock_type::now();
1836 		if (h.is_valid())
1837 		{
1838 			torrent_status const& s = view.get_active_torrent();
1839 
1840 			print((piece_bar(s.pieces, terminal_width - 2) + "\x1b[K\n").c_str());
1841 			pos += 1;
1842 
1843 			if ((print_downloads && s.state != torrent_status::seeding)
1844 				|| print_peers)
1845 				h.get_peer_info(peers);
1846 
1847 			if (print_peers && !peers.empty())
1848 			{
1849 				using lt::peer_info;
1850 				// sort connecting towards the bottom of the list, and by peer_id
1851 				// otherwise, to keep the list as stable as possible
1852 				std::sort(peers.begin(), peers.end()
1853 					, [](peer_info const& lhs, peer_info const& rhs)
1854 					{
1855 						{
1856 							bool const l = bool(lhs.flags & peer_info::connecting);
1857 							bool const r = bool(rhs.flags & peer_info::connecting);
1858 							if (l != r) return l < r;
1859 						}
1860 
1861 						{
1862 							bool const l = bool(lhs.flags & peer_info::handshake);
1863 							bool const r = bool(rhs.flags & peer_info::handshake);
1864 							if (l != r) return l < r;
1865 						}
1866 
1867 						return lhs.pid < rhs.pid;
1868 					});
1869 				pos += print_peer_info(out, peers, terminal_height - pos - 2);
1870 			}
1871 
1872 			if (print_trackers)
1873 			{
1874 				snprintf(str, sizeof(str), "next_announce: %4" PRId64 " | current tracker: %s\x1b[K\n"
1875 					, std::int64_t(duration_cast<seconds>(s.next_announce).count())
1876 					, s.current_tracker.c_str());
1877 				out += str;
1878 				pos += 1;
1879 				std::vector<lt::announce_entry> tr = h.trackers();
1880 				for (lt::announce_entry const& ae : h.trackers())
1881 				{
1882 					std::snprintf(str, sizeof(str), "%2d %-55s %s\x1b[K\n"
1883 						, ae.tier, ae.url.c_str(), ae.verified?"OK ":"-  ");
1884 					out += str;
1885 					pos += 1;
1886 					int idx = 0;
1887 					for (auto const& ep : ae.endpoints)
1888 					{
1889 						++idx;
1890 						if (!ep.enabled) continue;
1891 						if (pos + 1 >= terminal_height) break;
1892 
1893 						std::snprintf(str, sizeof(str), "  [%2d] fails: %-3d (%-3d) %s %5d \"%s\" %s\x1b[K\n"
1894 							, idx
1895 							, ep.fails, ae.fail_limit
1896 							, to_string(int(total_seconds(ep.next_announce - now)), 8).c_str()
1897 							, ep.min_announce > now ? int(total_seconds(ep.min_announce - now)) : 0
1898 							, ep.last_error ? ep.last_error.message().c_str() : ""
1899 							, ep.message.c_str());
1900 						out += str;
1901 						pos += 1;
1902 						// we only need to show this error once, not for every
1903 						// endpoint
1904 						if (ep.last_error == boost::asio::error::host_not_found) break;
1905 					}
1906 
1907 					if (pos + 1 >= terminal_height) break;
1908 				}
1909 			}
1910 
1911 			if (print_matrix)
1912 			{
1913 				int height_out = 0;
1914 				print(piece_matrix(s.pieces, terminal_width, &height_out).c_str());
1915 				pos += height_out;
1916 			}
1917 
1918 			if (print_downloads)
1919 			{
1920 				h.get_download_queue(queue);
1921 
1922 				std::sort(queue.begin(), queue.end()
1923 					, [] (lt::partial_piece_info const& lhs, lt::partial_piece_info const& rhs)
1924 					{ return lhs.piece_index < rhs.piece_index; });
1925 
1926 				std::sort(cs.pieces.begin(), cs.pieces.end()
1927 					, [](lt::cached_piece_info const& lhs, lt::cached_piece_info const& rhs)
1928 					{ return lhs.piece < rhs.piece; });
1929 
1930 				int p = 0; // this is horizontal position
1931 				for (lt::cached_piece_info const& i : cs.pieces)
1932 				{
1933 					if (pos + 3 >= terminal_height) break;
1934 
1935 					lt::partial_piece_info* pp = nullptr;
1936 					lt::partial_piece_info tmp;
1937 					tmp.piece_index = i.piece;
1938 					std::vector<lt::partial_piece_info>::iterator ppi
1939 						= std::lower_bound(queue.begin(), queue.end(), tmp
1940 						, [](lt::partial_piece_info const& lhs, lt::partial_piece_info const& rhs)
1941 						{ return lhs.piece_index < rhs.piece_index; });
1942 
1943 					if (ppi != queue.end() && ppi->piece_index == i.piece) pp = &*ppi;
1944 
1945 					print_piece(pp, &i, peers, out);
1946 
1947 					int num_blocks = pp ? pp->blocks_in_piece : int(i.blocks.size());
1948 					p += num_blocks + 8;
1949 					bool continuous_mode = 8 + num_blocks > terminal_width;
1950 					if (continuous_mode)
1951 					{
1952 						while (p > terminal_width)
1953 						{
1954 							p -= terminal_width;
1955 							++pos;
1956 						}
1957 					}
1958 					else if (p + num_blocks + 8 > terminal_width)
1959 					{
1960 						out += "\x1b[K\n";
1961 						pos += 1;
1962 						p = 0;
1963 					}
1964 
1965 					if (pp) queue.erase(ppi);
1966 				}
1967 
1968 				for (lt::partial_piece_info const& i : queue)
1969 				{
1970 					if (pos + 3 >= terminal_height) break;
1971 
1972 					print_piece(&i, nullptr, peers, out);
1973 
1974 					int num_blocks = i.blocks_in_piece;
1975 					p += num_blocks + 8;
1976 					bool continuous_mode = 8 + num_blocks > terminal_width;
1977 					if (continuous_mode)
1978 					{
1979 						while (p > terminal_width)
1980 						{
1981 							p -= terminal_width;
1982 							++pos;
1983 						}
1984 					}
1985 					else if (p + num_blocks + 8 > terminal_width)
1986 					{
1987 						out += "\x1b[K\n";
1988 						pos += 1;
1989 						p = 0;
1990 					}
1991 				}
1992 				if (p != 0)
1993 				{
1994 					out += "\x1b[K\n";
1995 					pos += 1;
1996 				}
1997 
1998 				std::snprintf(str, sizeof(str), "%s %s read cache | %s %s downloading | %s %s cached | %s %s flushed | %s %s snubbed | = requested\x1b[K\n"
1999 					, esc("34;7"), esc("0") // read cache
2000 					, esc("33;7"), esc("0") // downloading
2001 					, esc("36;7"), esc("0") // cached
2002 					, esc("32;7"), esc("0") // flushed
2003 					, esc("35;7"), esc("0") // snubbed
2004 					);
2005 				out += str;
2006 				pos += 1;
2007 			}
2008 
2009 			if (print_file_progress && s.has_metadata)
2010 			{
2011 				std::vector<std::int64_t> file_progress;
2012 				h.file_progress(file_progress);
2013 				std::vector<lt::open_file_state> file_status = h.file_status();
2014 				std::vector<lt::download_priority_t> file_prio = h.get_file_priorities();
2015 				auto f = file_status.begin();
2016 				std::shared_ptr<const lt::torrent_info> ti = h.torrent_file();
2017 
2018 				int p = 0; // this is horizontal position
2019 				for (file_index_t i(0); i < file_index_t(ti->num_files()); ++i)
2020 				{
2021 					int const idx = static_cast<int>(i);
2022 					if (pos + 1 >= terminal_height) break;
2023 
2024 					bool const pad_file = ti->files().pad_file_at(i);
2025 					if (pad_file && !show_pad_files) continue;
2026 
2027 					int const progress = ti->files().file_size(i) > 0
2028 						? int(file_progress[idx] * 1000 / ti->files().file_size(i)) : 1000;
2029 					assert(file_progress[idx] <= ti->files().file_size(i));
2030 
2031 					bool const complete = file_progress[idx] == ti->files().file_size(i);
2032 
2033 					std::string title = ti->files().file_name(i).to_string();
2034 					if (!complete)
2035 					{
2036 						std::snprintf(str, sizeof(str), " (%.1f%%)", progress / 10.f);
2037 						title += str;
2038 					}
2039 
2040 					if (f != file_status.end() && f->file_index == i)
2041 					{
2042 						title += " [ ";
2043 						if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::read_write) title += "read/write ";
2044 						else if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::read_only) title += "read ";
2045 						else if ((f->open_mode & lt::file_open_mode::rw_mask) == lt::file_open_mode::write_only) title += "write ";
2046 						if (f->open_mode & lt::file_open_mode::random_access) title += "random_access ";
2047 						if (f->open_mode & lt::file_open_mode::sparse) title += "sparse ";
2048 						title += "]";
2049 						++f;
2050 					}
2051 
2052 					const int file_progress_width = pad_file ? 10 : 65;
2053 
2054 					// do we need to line-break?
2055 					if (p + file_progress_width + 13 > terminal_width)
2056 					{
2057 						out += "\x1b[K\n";
2058 						pos += 1;
2059 						p = 0;
2060 					}
2061 
2062 					std::snprintf(str, sizeof(str), "%s %7s p: %d ",
2063 						progress_bar(progress, file_progress_width
2064 							, pad_file ? col_blue
2065 							: complete ? col_green : col_yellow
2066 							, '-', '#', title.c_str()).c_str()
2067 						, add_suffix(file_progress[idx]).c_str()
2068 						, static_cast<std::uint8_t>(file_prio[idx]));
2069 
2070 					p += file_progress_width + 13;
2071 					out += str;
2072 				}
2073 
2074 				if (p != 0)
2075 				{
2076 					out += "\x1b[K\n";
2077 					pos += 1;
2078 				}
2079 			}
2080 		}
2081 
2082 		if (print_log)
2083 		{
2084 			for (auto const& e : events)
2085 			{
2086 				if (pos + 1 >= terminal_height) break;
2087 				out += e;
2088 				out += "\x1b[K\n";
2089 				pos += 1;
2090 			}
2091 		}
2092 
2093 		// clear rest of screen
2094 		out += "\x1b[J";
2095 		print(out.c_str());
2096 
2097 		std::fflush(stdout);
2098 
2099 		if (!monitor_dir.empty() && next_dir_scan < now)
2100 		{
2101 			scan_dir(monitor_dir, ses);
2102 			next_dir_scan = now + seconds(poll_interval);
2103 		}
2104 	}
2105 
2106 	resume_data_loader.join();
2107 
2108 	ses.pause();
2109 	std::printf("saving resume data\n");
2110 
2111 	// get all the torrent handles that we need to save resume data for
2112 	std::vector<torrent_status> const temp = ses.get_torrent_status(
2113 		[](torrent_status const& st)
2114 		{
2115 			if (!st.handle.is_valid() || !st.has_metadata || !st.need_save_resume)
2116 				return false;
2117 			return true;
2118 		}, {});
2119 
2120 	int idx = 0;
2121 	for (auto const& st : temp)
2122 	{
2123 		// save_resume_data will generate an alert when it's done
2124 		st.handle.save_resume_data(torrent_handle::save_info_dict);
2125 		++num_outstanding_resume_data;
2126 		++idx;
2127 		if ((idx % 32) == 0)
2128 		{
2129 			std::printf("\r%d  ", num_outstanding_resume_data);
2130 			pop_alerts(view, ses_view, ses, events);
2131 		}
2132 	}
2133 	std::printf("\nwaiting for resume data [%d]\n", num_outstanding_resume_data);
2134 
2135 	while (num_outstanding_resume_data > 0)
2136 	{
2137 		alert const* a = ses.wait_for_alert(seconds(10));
2138 		if (a == nullptr) continue;
2139 		pop_alerts(view, ses_view, ses, events);
2140 	}
2141 
2142 	if (g_log_file) std::fclose(g_log_file);
2143 
2144 	// we're just saving the DHT state
2145 #ifndef TORRENT_DISABLE_DHT
2146 	std::printf("\nsaving session state\n");
2147 	{
2148 		lt::entry session_state;
2149 		ses.save_state(session_state, lt::session::save_dht_state);
2150 
2151 		std::vector<char> out;
2152 		bencode(std::back_inserter(out), session_state);
2153 		save_file(".ses_state", out);
2154 	}
2155 #endif
2156 
2157 	std::printf("closing session\n");
2158 
2159 	return 0;
2160 }
2161 
2162