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