diff --git a/examples/Jamfile b/examples/Jamfile index 257f29921..c8c659388 100644 --- a/examples/Jamfile +++ b/examples/Jamfile @@ -16,7 +16,7 @@ project client_test static ; -exe client_test : client_test.cpp ; +exe client_test : client_test.cpp print.cpp torrent_view.cpp ; exe simple_client : simple_client.cpp ; exe stats_counters : stats_counters.cpp ; diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 19e5f54c9..da2b0a332 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -64,164 +64,13 @@ POSSIBILITY OF SUCH DAMAGE. #include "libtorrent/time.hpp" #include "libtorrent/create_torrent.hpp" +#include "torrent_view.hpp" +#include "print.hpp" + using boost::bind; using libtorrent::total_milliseconds; -void terminal_size(int* terminal_width, int* terminal_height) -{ #ifdef _WIN32 - HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); - CONSOLE_SCREEN_BUFFER_INFO coninfo; - if (GetConsoleScreenBufferInfo(out, &coninfo)) - { - *terminal_width = coninfo.dwSize.X; - *terminal_height = coninfo.srWindow.Bottom - coninfo.srWindow.Top; -#else - int tty = open("/dev/tty", O_RDONLY); - winsize size; - int ret = ioctl(tty, TIOCGWINSZ, (char*)&size); - close(tty); - if (ret == 0) - { - *terminal_width = size.ws_col; - *terminal_height = size.ws_row; -#endif - - if (*terminal_width < 64) - *terminal_width = 64; - if (*terminal_height < 25) - *terminal_height = 25; - } - else - { - *terminal_width = 190; - *terminal_height = 100; - } -} - -#ifdef WIN32 -void apply_ansi_code(int* attributes, bool* reverse, int code) -{ - const static int color_table[8] = - { - 0, // black - FOREGROUND_RED, // red - FOREGROUND_GREEN, // green - FOREGROUND_RED | FOREGROUND_GREEN, // yellow - FOREGROUND_BLUE, // blue - FOREGROUND_RED | FOREGROUND_BLUE, // magenta - FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan - FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // white - }; - - enum - { - foreground_mask = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, - background_mask = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE - }; - - const static int fg_mask[2] = {foreground_mask, background_mask}; - const static int bg_mask[2] = {background_mask, foreground_mask}; - const static int fg_shift[2] = { 0, 4}; - const static int bg_shift[2] = { 4, 0}; - - if (code == 0) - { - // reset - *attributes = color_table[7]; - *reverse = false; - } - else if (code == 7) - { - if (*reverse) return; - *reverse = true; - int fg_col = *attributes & foreground_mask; - int bg_col = (*attributes & background_mask) >> 4; - *attributes &= ~(foreground_mask + background_mask); - *attributes |= fg_col << 4; - *attributes |= bg_col; - } - else if (code >= 30 && code <= 37) - { - // foreground color - *attributes &= ~fg_mask[*reverse]; - *attributes |= color_table[code - 30] << fg_shift[*reverse]; - } - else if (code >= 40 && code <= 47) - { - // foreground color - *attributes &= ~bg_mask[*reverse]; - *attributes |= color_table[code - 40] << bg_shift[*reverse]; - } -} -#endif - -void print_with_ansi_colors(char const* str) -{ -#ifdef WIN32 - HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); - - // first, clear the console and move the cursor - // to the top left corner - CONSOLE_SCREEN_BUFFER_INFO si; - GetConsoleScreenBufferInfo(out, &si); - COORD c = {0, 0}; - DWORD n; - FillConsoleOutputCharacter(out, ' ', si.dwSize.X * si.dwSize.Y, c, &n); - FillConsoleOutputAttribute(out, 0x7, si.dwSize.X * si.dwSize.Y, c, &n); - SetConsoleCursorPosition(out, c); - - char* buf = (char*)str; - - int current_attributes = 7; - bool reverse = false; - SetConsoleTextAttribute(out, current_attributes); - - char* start = buf; - DWORD written; - while (*buf != 0) - { - if (*buf == '\033' && buf[1] == '[') - { - *buf = 0; - WriteFile(out, start, buf - start, &written, NULL); - buf += 2; // skip escape and '[' - start = buf; - one_more: - while (*buf != 'm' && *buf != ';' && *buf != 0) ++buf; - if (*buf == 0) break; - int code = atoi(start); - apply_ansi_code(¤t_attributes, &reverse, code); - if (*buf == ';') - { - ++buf; - start = buf; - goto one_more; - } - SetConsoleTextAttribute(out, current_attributes); - ++buf; // skip 'm' - start = buf; - } - else - { - ++buf; - } - } - WriteFile(out, start, buf - start, &written, NULL); - -#else - // first, clear the console and move the cursor - // to the top left corner - puts("\033[2J\033[0;0H"); - puts(str); -#endif -} - -#ifdef _WIN32 - -#if defined(_MSC_VER) -# define for if (false) {} else for -#endif #include #include @@ -327,6 +176,8 @@ bool print_disk_stats = false; // without having received a response (successful or failure) int num_outstanding_resume_data = 0; +torrent_view view; + int load_file(std::string const& filename, std::vector& v, libtorrent::error_code& ec, int limit = 8000000) { ec.clear(); @@ -415,22 +266,6 @@ std::string print_endpoint(libtorrent::tcp::endpoint const& ep) return buf; } -enum { - torrents_all, - torrents_downloading, - torrents_not_paused, - torrents_seeding, - torrents_queued, - torrents_stopped, - torrents_checking, - torrents_loaded, - torrents_feeds, - - torrents_max -}; - -int torrent_filter = torrents_not_paused; - struct torrent_entry { torrent_entry(libtorrent::torrent_handle h) : handle(h) {} @@ -446,203 +281,11 @@ files_t hash_to_filename; using libtorrent::torrent_status; -bool show_torrent(libtorrent::torrent_status const& st, int torrent_filter, int* counters) -{ - ++counters[torrents_all]; - - if (!st.paused - && st.state != torrent_status::seeding - && st.state != torrent_status::finished) - { - ++counters[torrents_downloading]; - } - - if (!st.paused) ++counters[torrents_not_paused]; - - if (!st.paused - && (st.state == torrent_status::seeding - || st.state == torrent_status::finished)) - { - ++counters[torrents_seeding]; - } - - if (st.paused && st.auto_managed) - { - ++counters[torrents_queued]; - } - - if (st.paused && !st.auto_managed) - { - ++counters[torrents_stopped]; - } - - if (st.state == torrent_status::checking_files) - { - ++counters[torrents_checking]; - } - - if (st.is_loaded) - { - ++counters[torrents_loaded]; - } - - switch (torrent_filter) - { - case torrents_all: return true; - case torrents_downloading: - return !st.paused - && st.state != torrent_status::seeding - && st.state != torrent_status::finished; - case torrents_not_paused: return !st.paused; - case torrents_seeding: - return !st.paused - && (st.state == torrent_status::seeding - || st.state == torrent_status::finished); - case torrents_queued: return st.paused && st.auto_managed; - case torrents_stopped: return st.paused && !st.auto_managed; - case torrents_checking: return st.state == torrent_status::checking_files; - case torrents_loaded: return st.is_loaded; - case torrents_feeds: return false; - } - return true; -} - bool yes(libtorrent::torrent_status const&) { return true; } FILE* g_log_file = 0; -int active_torrent = 0; - -bool compare_torrent(torrent_status const* lhs, torrent_status const* rhs) -{ - if (lhs->queue_position != -1 && rhs->queue_position != -1) - { - // both are downloading, sort by queue pos - return lhs->queue_position < rhs->queue_position; - } - else if (lhs->queue_position == -1 && rhs->queue_position == -1) - { - // both are seeding, sort by seed-rank - if (lhs->seed_rank != rhs->seed_rank) - return lhs->seed_rank > rhs->seed_rank; - - return lhs->info_hash < rhs->info_hash; - } - - return (lhs->queue_position == -1) < (rhs->queue_position == -1); -} - -void update_filtered_torrents(boost::unordered_set& all_handles - , std::vector& filtered_handles, int* counters) -{ - filtered_handles.clear(); - memset(counters, 0, sizeof(int) * torrents_max); - for (boost::unordered_set::iterator i = all_handles.begin() - , end(all_handles.end()); i != end; ++i) - { - if (!show_torrent(*i, torrent_filter, counters)) continue; - filtered_handles.push_back(&*i); - } - if (active_torrent >= int(filtered_handles.size())) active_torrent = filtered_handles.size() - 1; - else if (active_torrent == -1 && !filtered_handles.empty()) active_torrent = 0; - std::sort(filtered_handles.begin(), filtered_handles.end(), &compare_torrent); -} - -char const* esc(char const* code) -{ - // this is a silly optimization - // to avoid copying of strings - enum { num_strings = 200 }; - static char buf[num_strings][20]; - static int round_robin = 0; - char* ret = buf[round_robin]; - ++round_robin; - if (round_robin >= num_strings) round_robin = 0; - ret[0] = '\033'; - ret[1] = '['; - int i = 2; - int j = 0; - while (code[j]) ret[i++] = code[j++]; - ret[i++] = 'm'; - ret[i++] = 0; - return ret; -} - -enum color_code -{ - col_none = -1, - col_black = 0, - col_red = 1, - col_green = 2, - col_yellow = 3, - col_blue = 4, - col_magenta = 5, - col_cyan = 6, - col_white = 7, -}; - -std::string color(std::string const& s, color_code c) -{ - if (c == col_none) return s; - - char buf[1024]; - snprintf(buf, sizeof(buf), "\x1b[3%dm%s\x1b[39m", c, s.c_str()); - return buf; -} - -std::string to_string(int v, int width) -{ - char buf[100]; - snprintf(buf, sizeof(buf), "%*d", width, v); - return buf; -} - -std::string& to_string(float v, int width, int precision = 3) -{ - // this is a silly optimization - // to avoid copying of strings - enum { num_strings = 20 }; - static std::string buf[num_strings]; - static int round_robin = 0; - std::string& ret = buf[round_robin]; - ++round_robin; - if (round_robin >= num_strings) round_robin = 0; - ret.resize(20); - int size = snprintf(&ret[0], 20, "%*.*f", width, precision, v); - ret.resize((std::min)(size, width)); - return ret; -} - -std::string add_suffix(float val, char const* suffix = 0) -{ - std::string ret; - if (val == 0) - { - ret.resize(4 + 2, ' '); - if (suffix) ret.resize(4 + 2 + strlen(suffix), ' '); - return ret; - } - - const char* prefix[] = {"kB", "MB", "GB", "TB"}; - const int num_prefix = sizeof(prefix) / sizeof(const char*); - for (int i = 0; i < num_prefix; ++i) - { - val /= 1000.f; - if (std::fabs(val) < 1000.f) - { - ret = to_string(val, 4); - ret += prefix[i]; - if (suffix) ret += suffix; - return ret; - } - } - ret = to_string(val, 4); - ret += "PB"; - if (suffix) ret += suffix; - return ret; -} - std::string const& piece_bar(libtorrent::bitfield const& p, int width) { const int table_size = 18; @@ -679,41 +322,6 @@ std::string const& piece_bar(libtorrent::bitfield const& p, int width) return bar; } -std::string const& progress_bar(int progress, int width, color_code c = col_green - , char fill = '#', char bg = '-', std::string caption = "") -{ - static std::string bar; - bar.clear(); - bar.reserve(width + 10); - - int progress_chars = (progress * width + 500) / 1000; - - if (caption.empty()) - { - char code[10]; - snprintf(code, sizeof(code), "\x1b[3%dm", c); - bar = code; - std::fill_n(std::back_inserter(bar), progress_chars, fill); - std::fill_n(std::back_inserter(bar), width - progress_chars, bg); - bar += esc("39"); - } - else - { - // foreground color (depends a bit on background color) - color_code tc = col_black; - if (c == col_black || c == col_blue) - tc = col_white; - - caption.resize(width, ' '); - - char str[256]; - snprintf(str, sizeof(str), "\x1b[4%d;3%dm%s\x1b[48;5;238m\x1b[37m%s\x1b[49;39m" - , c, tc, caption.substr(0, progress_chars).c_str(), caption.substr(progress_chars).c_str()); - bar = str; - } - return bar; -} - int peer_index(libtorrent::tcp::endpoint addr, std::vector const& peers) { using namespace libtorrent; @@ -928,12 +536,12 @@ bool seed_mode = false; bool share_mode = false; bool disable_storage = false; -int loop_limit = 0; +bool quit = false; void signal_handler(int signo) { // make the main loop terminate - loop_limit = 1; + quit = true; } void load_torrent(libtorrent::sha1_hash const& ih, std::vector& buf, libtorrent::error_code& ec) @@ -1077,13 +685,6 @@ void scan_dir(std::string const& dir_path } } -torrent_status const& get_active_torrent(std::vector const& filtered_handles) -{ - if (active_torrent >= int(filtered_handles.size()) - || active_torrent < 0) active_torrent = 0; - return *filtered_handles[active_torrent]; -} - char const* timestamp() { time_t t = std::time(0); @@ -1135,9 +736,7 @@ int save_file(std::string const& filename, std::vector& v) // returns false if the alert was not handled bool handle_alert(libtorrent::session& ses, libtorrent::alert* a , handles_t& files, std::set& non_files - , int* counters, boost::unordered_set& all_handles - , std::vector& filtered_handles - , bool& need_resort, std::vector& stats_counters) + , std::vector& stats_counters) { using namespace libtorrent; @@ -1188,7 +787,8 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a // also, add it to the files map, and remove it from the non_files list // to keep the scan dir logic in sync so it's not removed, or added twice torrent_handle h = p->handle; - if (h.is_valid()) { + if (h.is_valid()) + { boost::shared_ptr ti = h.torrent_file(); create_torrent ct(*ti); entry te = ct.generate(); @@ -1316,30 +916,7 @@ bool handle_alert(libtorrent::session& ses, libtorrent::alert* a } else if (state_update_alert* p = alert_cast(a)) { - bool need_filter_update = false; - for (std::vector::iterator i = p->status.begin(); - i != p->status.end(); ++i) - { - boost::unordered_set::iterator j = all_handles.find(*i); - // add new entries here - if (j == all_handles.end()) - { - j = all_handles.insert(*i).first; - if (show_torrent(*j, torrent_filter, counters)) - { - filtered_handles.push_back(&*j); - need_resort = true; - } - } - if (j->state != i->state - || j->paused != i->paused - || j->auto_managed != i->auto_managed) - need_filter_update = true; - ((torrent_status&)*j) = *i; - } - if (need_filter_update) - update_filtered_torrents(all_handles, filtered_handles, counters); - + view.update_torrents(p->status); return true; } return false; @@ -1409,25 +986,6 @@ void print_piece(libtorrent::partial_piece_info* pp out += str; } -static char const* state_str[] = - {"checking (q)", "checking", "dl metadata" - , "downloading", "finished", "seeding", "allocating", "checking (r)"}; - -std::string torrent_state(torrent_status const& s) -{ - if (!s.error.empty()) return s.error; - std::string ret; - if (s.paused && !s.auto_managed) ret += "paused"; - else if (s.paused && s.auto_managed) ret += "queued"; - else if (s.upload_mode) ret += "upload mode"; - else ret += state_str[s.state]; - if (!s.paused && !s.auto_managed) ret += " [F]"; - char buf[10]; - snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.f); - ret += buf; - return ret; -} - int main(int argc, char* argv[]) { if (argc == 1) @@ -1441,8 +999,6 @@ int main(int argc, char* argv[]) " -t sets the scan interval of the monitor dir\n" " -F sets the UI refresh rate. This is the number of\n" " milliseconds between screen refreshes.\n" - " -q automatically quit the client after of refreshes\n" - " this is useful for scripting tests\n" " -k enable high performance settings. This overwrites any other\n" " previous command line options, so be sure to specify this first\n" " -G Add torrents in seed-mode (i.e. assume all pieces\n" @@ -1542,17 +1098,11 @@ int main(int argc, char* argv[]) // it was added through the directory monitor. It is used to // be able to remove torrents that were added via the directory // monitor when they're not in the directory anymore. - boost::unordered_set all_handles; - std::vector filtered_handles; - handles_t files; // torrents that were not added via the monitor dir std::set non_files; - int counters[torrents_max]; - memset(counters, 0, sizeof(counters)); - libtorrent::session ses(fingerprint("LT", LIBTORRENT_VERSION_MAJOR, LIBTORRENT_VERSION_MINOR, 0, 0) , lt::session::add_default_plugins | lt::session::start_default_features , alert::all_categories @@ -1787,7 +1337,6 @@ int main(int argc, char* argv[]) settings.set_int(settings_pack::active_seeds, atoi(arg)); settings.set_int(settings_pack::active_limit, atoi(arg) * 2); break; - case 'q': loop_limit = atoi(arg); break; case '0': disable_storage = true; --i; } ++i; // skip the argument @@ -1887,37 +1436,12 @@ int main(int argc, char* argv[]) signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); #endif - while (loop_limit > 1 || loop_limit == 0) + while (!quit) { ++tick; ses.post_torrent_updates(); ses.post_session_stats(); - if (active_torrent >= int(filtered_handles.size())) active_torrent = filtered_handles.size() - 1; - if (active_torrent >= 0) - { - // ask for distributed copies for the selected torrent. Since this - // is a somewhat expensive operation, don't do it by default for - // all torrents - torrent_status const& h = *filtered_handles[active_torrent]; - h.handle.status( - torrent_handle::query_distributed_copies - | torrent_handle::query_pieces - | torrent_handle::query_verified_pieces); - } - std::vector feeds; - ses.get_feeds(feeds); - - counters[torrents_feeds] = feeds.size(); - - std::sort(filtered_handles.begin(), filtered_handles.end(), &compare_torrent); - - if (loop_limit > 1) - { - // in test mode, don't quit until we're seeding - if (loop_limit > 2 || active_torrent == -1 || filtered_handles[active_torrent]->is_seeding) - --loop_limit; - } int c = 0; if (sleep_and_input(&c, refresh_delay)) @@ -1937,6 +1461,8 @@ int main(int argc, char* argv[]) #define DOWN_ARROW 66 #endif + torrent_handle h = view.get_active_handle(); + if (c == EOF) { break; } do { @@ -1955,33 +1481,36 @@ int main(int argc, char* argv[]) if (c == LEFT_ARROW) { // arrow left - if (torrent_filter > 0) + int filter = view.filter(); + if (filter > 0) { - --torrent_filter; - update_filtered_torrents(all_handles, filtered_handles, counters); + --filter; + view.set_filter(filter); + h = view.get_active_handle(); } } else if (c == RIGHT_ARROW) { // arrow right - if (torrent_filter < torrents_max - 1) + int filter = view.filter(); + if (filter < torrent_view::torrents_max - 1) { - ++torrent_filter; - update_filtered_torrents(all_handles, filtered_handles, counters); + ++filter; + view.set_filter(filter); + h = view.get_active_handle(); } } else if (c == UP_ARROW) { // arrow up - --active_torrent; - if (active_torrent < 0) active_torrent = 0; + view.arrow_up(); + h = view.get_active_handle(); } else if (c == DOWN_ARROW) { // arrow down - ++active_torrent; - if (active_torrent >= int(filtered_handles.size())) - active_torrent = filtered_handles.size() - 1; + view.arrow_down(); + h = view.get_active_handle(); } } @@ -2026,39 +1555,36 @@ int main(int argc, char* argv[]) if (c == 'q') break; - if (c == 'D') + if (c == 'D' && h.is_valid()) { - torrent_status const& st = get_active_torrent(filtered_handles); - if (st.handle.is_valid()) + torrent_status const& st = view.get_active_torrent(); + printf("\n\nARE YOU SURE YOU WANT TO DELETE THE FILES FOR '%s'. THIS OPERATION CANNOT BE UNDONE. (y/N)" + , st.name.c_str()); + char response = 'n'; + scanf("%c", &response); + if (response == 'y') { - printf("\n\nARE YOU SURE YOU WANT TO DELETE THE FILES FOR '%s'. THIS OPERATION CANNOT BE UNDONE. (y/N)" - , st.name.c_str()); - char response = 'n'; - scanf("%c", &response); - if (response == 'y') + // also delete the .torrent file from the torrent directory + handles_t::iterator i = std::find_if(files.begin(), files.end() + , boost::bind(&handles_t::value_type::second, _1) == st.handle); + if (i != files.end()) { - // also delete the .torrent file from the torrent directory - handles_t::iterator i = std::find_if(files.begin(), files.end() - , boost::bind(&handles_t::value_type::second, _1) == st.handle); - if (i != files.end()) - { - error_code ec; - std::string path; - if (is_complete(i->first)) path = i->first; - else path = combine_path(monitor_dir, i->first); - remove(path, ec); - if (ec) printf("failed to delete .torrent file: %s\n", ec.message().c_str()); - files.erase(i); - } - if (st.handle.is_valid()) - ses.remove_torrent(st.handle, lt::session::delete_files); + error_code ec; + std::string path; + if (is_complete(i->first)) path = i->first; + else path = combine_path(monitor_dir, i->first); + remove(path, ec); + if (ec) printf("failed to delete .torrent file: %s\n", ec.message().c_str()); + files.erase(i); } + if (st.handle.is_valid()) + ses.remove_torrent(st.handle, lt::session::delete_files); } } - if (c == 'j' && !filtered_handles.empty()) + if (c == 'j' && h.is_valid()) { - get_active_torrent(filtered_handles).handle.force_recheck(); + h.force_recheck(); } if (c == 'x') @@ -2066,74 +1592,74 @@ int main(int argc, char* argv[]) print_disk_stats = !print_disk_stats; } - if (c == 'r' && !filtered_handles.empty()) + if (c == 'r' && h.is_valid()) { - get_active_torrent(filtered_handles).handle.force_reannounce(); + h.force_reannounce(); } - if (c == 's' && !filtered_handles.empty()) + if (c == 's' && h.is_valid()) { - torrent_status const& ts = get_active_torrent(filtered_handles); - ts.handle.set_sequential_download(!ts.sequential_download); + torrent_status const& ts = view.get_active_torrent(); + h.set_sequential_download(!ts.sequential_download); } if (c == 'R') { // save resume data for all torrents - for (std::vector::iterator i = filtered_handles.begin() - , end(filtered_handles.end()); i != end; ++i) + std::vector torrents; + ses.get_torrent_status(&torrents, &yes, 0); + for (std::vector::iterator i = torrents.begin() + , end(torrents.end()); i != end; ++i) { - if ((*i)->need_save_resume) + if (i->need_save_resume) { - (*i)->handle.save_resume_data(); + i->handle.save_resume_data(); ++num_outstanding_resume_data; } } } - if (c == 'o' && !filtered_handles.empty()) + if (c == 'o' && h.is_valid()) { - torrent_status const& ts = get_active_torrent(filtered_handles); + torrent_status const& ts = view.get_active_torrent(); int num_pieces = ts.num_pieces; if (num_pieces > 300) num_pieces = 300; for (int i = 0; i < num_pieces; ++i) { - ts.handle.set_piece_deadline(i, (i+5) * 1000, torrent_handle::alert_when_available); + h.set_piece_deadline(i, (i+5) * 1000, torrent_handle::alert_when_available); } } - if (c == 'v' && !filtered_handles.empty()) + if (c == 'v' && h.is_valid()) { - torrent_status const& ts = get_active_torrent(filtered_handles); - ts.handle.scrape_tracker(); + h.scrape_tracker(); } - if (c == 'p' && !filtered_handles.empty()) + if (c == 'p' && h.is_valid()) { - torrent_status const& ts = get_active_torrent(filtered_handles); + torrent_status const& ts = view.get_active_torrent(); if (!ts.auto_managed && ts.paused) { - ts.handle.auto_managed(true); + h.auto_managed(true); } else { - ts.handle.auto_managed(false); - ts.handle.pause(torrent_handle::graceful_pause); + h.auto_managed(false); + h.pause(torrent_handle::graceful_pause); } } // toggle force-start - if (c == 'k' && !filtered_handles.empty()) + if (c == 'k' && h.is_valid()) { - torrent_status const& ts = get_active_torrent(filtered_handles); - ts.handle.auto_managed(!ts.auto_managed); - if (ts.auto_managed && ts.paused) ts.handle.resume(); + torrent_status const& ts = view.get_active_torrent(); + h.auto_managed(!ts.auto_managed); + if (ts.auto_managed && ts.paused) h.resume(); } - if (c == 'c' && !filtered_handles.empty()) + if (c == 'c' && h.is_valid()) { - torrent_status const& ts = get_active_torrent(filtered_handles); - ts.handle.clear_error(); + h.clear_error(); } // toggle displays @@ -2154,24 +1680,14 @@ int main(int argc, char* argv[]) if (c == '5') print_peer_rate = !print_peer_rate; if (c == '6') print_fails = !print_fails; if (c == '7') print_send_bufs = !print_send_bufs; - if (c == 'y') - { - char url[2048]; - puts("Enter RSS feed URL:\n"); - scanf("%2048s", url); - feed_settings set; - set.url = url; - set.add_args.save_path = save_path; - feed_handle h = ses.add_feed(set); - h.update_feed(); - } if (c == 'h') { - print_with_ansi_colors( + clear_screen(); + set_cursor_pos(0,0); + print( "HELP SCREEN (press any key to dismiss)\n\n" "CLIENT OPTIONS\n" "[q] quit client [m] add magnet link\n" - "[y] add RSS feed\n" "\n" "TORRENT ACTIONS\n" "[p] pause/unpause selected torrent\n" @@ -2209,7 +1725,7 @@ int main(int argc, char* argv[]) terminal_size(&terminal_width, &terminal_height); - int max_lines = terminal_height - 15; + view.set_max_size(terminal_height - 15); // loop through the alert queue to see if anything has happened. std::deque alerts; @@ -2218,11 +1734,10 @@ int main(int argc, char* argv[]) for (std::deque::iterator i = alerts.begin() , end(alerts.end()); i != end; ++i) { - bool need_resort = false; TORRENT_TRY { - if (!::handle_alert(ses, *i, files, non_files, counters - , all_handles, filtered_handles, need_resort, stats_counters)) + if (!::handle_alert(ses, *i, files, non_files + , stats_counters)) { // if we didn't handle the alert, print it to the log std::string event_string; @@ -2231,198 +1746,21 @@ int main(int argc, char* argv[]) if (events.size() >= 20) events.pop_front(); } } TORRENT_CATCH(std::exception& e) {} - - if (need_resort) - { - std::sort(filtered_handles.begin(), filtered_handles.end() - , &compare_torrent); - } - delete *i; } alerts.clear(); session_status sess_stat = ses.status(); - // in test mode, also quit when we loose the last peer - if (loop_limit > 1 && sess_stat.num_peers == 0 && tick > 30) break; - std::string out; - char const* filter_names[] = { "all", "downloading", "non-paused", "seeding", "queued", "stopped", "checking", "loaded", "RSS"}; - for (int i = 0; i < int(sizeof(filter_names)/sizeof(filter_names[0])); ++i) - { - char filter[200]; - snprintf(filter, sizeof(filter), "%s[%s (%d)]%s", torrent_filter == i?esc("7"):"" - , filter_names[i], counters[i], torrent_filter == i?esc("0"):""); - out += filter; - } - out += '\n'; - char str[500]; - int torrent_index = 0; - int lines_printed = 3; - if (torrent_filter == torrents_feeds) - { - for (std::vector::iterator i = feeds.begin() - , end(feeds.end()); i != end; ++i) - { - if (lines_printed >= max_lines) - { - out += "...\n"; - break; - } - - feed_status st = i->get_feed_status(); - if (st.url.size() > 70) st.url.resize(70); - - char update_timer[20]; - snprintf(update_timer, sizeof(update_timer), "%d", st.next_update); - snprintf(str, sizeof(str), "%-70s %s (%2d) %s\n", st.url.c_str() - , st.updating ? "updating" : update_timer - , int(st.items.size()) - , st.error ? st.error.message().c_str() : ""); - out += str; - ++lines_printed; - } - } - - // handle scrolling down when moving the cursor - // below the fold - static int start_offset = 0; - if (active_torrent >= max_lines - lines_printed - start_offset) - start_offset = active_torrent - max_lines + lines_printed + 1; - if (active_torrent < start_offset) start_offset = active_torrent; - - // print title bar for torrent list - snprintf(str, sizeof(str), " %-3s %-50s %-35s %-17s %-17s %-11s %-6s %-6s %-4s\n" - , "#", "Name", "Progress", "Download", "Upload", "Peers (D:S)", "Down", "Up", "Flags"); - out += str; - - for (std::vector::iterator i = filtered_handles.begin(); - i != filtered_handles.end(); ++torrent_index) - { - if (torrent_index < start_offset) - { - ++i; - continue; - } - if (lines_printed >= max_lines) - { - out += "...\n"; - break; - } - - torrent_status const& s = **i; - if (!s.handle.is_valid()) - { - i = filtered_handles.erase(i); - continue; - } - else - { - ++i; - } - - // the active torrent is highligted in the list - // this inverses the forground and background colors - char const* selection = ""; - if (active_torrent == torrent_index) - { - selection = "\x1b[1m\x1b[44m"; - out += selection; - } - - char queue_pos[16]; - if (s.queue_position == -1) - strcpy(queue_pos, "-"); - else - snprintf(queue_pos, sizeof(queue_pos), "%d", s.queue_position); - - std::string name = s.name; - if (name.size() > 50) name.resize(50); - -// int seeds = 0; -// int downloaders = 0; - -// if (s.num_complete >= 0) seeds = s.num_complete; -// else seeds = s.list_seeds; - -// if (s.num_incomplete >= 0) downloaders = s.num_incomplete; -// else downloaders = s.list_peers - s.list_seeds; - - color_code progress_bar_color = col_yellow; - if (!s.error.empty()) progress_bar_color = col_red; - else if (s.paused) progress_bar_color = col_blue; - else if (s.state == torrent_status::downloading_metadata) - progress_bar_color = col_magenta; - else if (s.current_tracker.empty()) - progress_bar_color = col_red; - else if (sess_stat.has_incoming_connections) - progress_bar_color = col_green; - - snprintf(str, sizeof(str), "%c%-3s %-50s %s%s %s (%s) %s (%s) %5d:%-5d" - " %s %s %c%s\n" - , s.is_loaded ? 'L' : ' ' - , queue_pos - , name.c_str() - , progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str() - , selection - , color(add_suffix(s.download_rate, "/s"), col_green).c_str() - , color(add_suffix(s.total_download), col_green).c_str() - , color(add_suffix(s.upload_rate, "/s"), col_red).c_str() - , color(add_suffix(s.total_upload), col_red).c_str() - , s.num_peers - s.num_seeds, s.num_seeds - , color(add_suffix(s.all_time_download), col_green).c_str() - , color(add_suffix(s.all_time_upload), col_red).c_str() - , s.need_save_resume?'S':' ', esc("0")); - out += str; - ++lines_printed; - - // if this is the selected torrent, restore the background color - if (active_torrent == torrent_index) - out += esc("0"); - - // don't print the piece bar if we don't have any piece, or if we have all - if (print_piece_bar && s.num_pieces != 0 && s.progress_ppm != 1000000) - { - out += " "; - out += piece_bar(s.pieces, terminal_width - 7); - out += "\n"; - ++lines_printed; - if (s.seed_mode) - { - out += " "; - out += piece_bar(s.verified_pieces, terminal_width - 7); - out += "\n"; - ++lines_printed; - } - } -/* - if (torrent_index != active_torrent) continue; - - boost::posix_time::time_duration t = s.next_announce; - snprintf(str, sizeof(str) - , " peers: %s%d%s (%s%d%s) seeds: %s%d%s distributed copies: %s%4.2f%s " - "sparse regions: %d download: %s%s%s next announce: %s%02d:%02d:%02d%s " - "tracker: %s%s%s\n" - , esc("37"), s.num_peers, esc("0") - , esc("37"), s.connect_candidates, esc("0") - , esc("37"), s.num_seeds, esc("0") - , esc("37"), s.distributed_copies, esc("0") - , s.sparse_regions - , esc("32"), add_suffix(s.download_rate, "/s").c_str(), esc("0") - , esc("37"), int(t.hours()), int(t.minutes()), int(t.seconds()), esc("0") - , esc("36"), s.current_tracker.c_str(), esc("0")); - out += str; - ++lines_printed; -*/ - } + clear_below(view.height()); + set_cursor_pos(0, view.height()); int cache_flags = print_downloads ? 0 : lt::session::disk_cache_no_pieces; - torrent_handle h; - if (!filtered_handles.empty()) h = get_active_torrent(filtered_handles).handle; + torrent_handle h = view.get_active_handle(); cache_status cs; ses.get_cache_info(&cs, h, cache_flags); @@ -2564,18 +1902,16 @@ int main(int argc, char* argv[]) out += str; } - torrent_status const* st = 0; - if (!filtered_handles.empty()) st = &get_active_torrent(filtered_handles); if (h.is_valid()) { - torrent_status const& s = *st; + torrent_status const& s = view.get_active_torrent(); if ((print_downloads && s.state != torrent_status::seeding) || print_peers) h.get_peer_info(peers); out += "====== "; - out += st->name; + out += s.name; out += " ======\n"; if (print_peers && !peers.empty()) @@ -2621,7 +1957,7 @@ int main(int argc, char* argv[]) < boost::bind(&partial_piece_info::piece_index, _2)); if (ppi != queue.end() && ppi->piece_index == i->piece) pp = &*ppi; - print_piece(pp, &*i, peers, st, out); + print_piece(pp, &*i, peers, &s, out); if (pp) queue.erase(ppi); } @@ -2629,7 +1965,7 @@ int main(int argc, char* argv[]) for (std::vector::iterator i = queue.begin() , end(queue.end()); i != end; ++i) { - print_piece(&*i, 0, peers, st, out); + print_piece(&*i, 0, peers, &s, out); } snprintf(str, sizeof(str), "%s %s: read cache %s %s: downloading %s %s: cached %s %s: flushed\n" , esc("34;7"), esc("0") // read cache @@ -2712,7 +2048,7 @@ int main(int argc, char* argv[]) } } - print_with_ansi_colors(out.c_str()); + print(out.c_str()); fflush(stdout); if (!monitor_dir.empty() diff --git a/examples/print.cpp b/examples/print.cpp new file mode 100644 index 000000000..9109924c5 --- /dev/null +++ b/examples/print.cpp @@ -0,0 +1,298 @@ +#ifdef _WIN32 + +#include +#include + +#else + +#include // for close() +#include // for open() +#include + +#endif + +#include "print.hpp" + +#include + +char const* esc(char const* code) +{ + // this is a silly optimization + // to avoid copying of strings + enum { num_strings = 200 }; + static char buf[num_strings][20]; + static int round_robin = 0; + char* ret = buf[round_robin]; + ++round_robin; + if (round_robin >= num_strings) round_robin = 0; + ret[0] = '\033'; + ret[1] = '['; + int i = 2; + int j = 0; + while (code[j]) ret[i++] = code[j++]; + ret[i++] = 'm'; + ret[i++] = 0; + return ret; +} + +std::string to_string(int v, int width) +{ + char buf[100]; + snprintf(buf, sizeof(buf), "%*d", width, v); + return buf; +} + +std::string add_suffix(float val, char const* suffix) +{ + std::string ret; + if (val == 0) + { + ret.resize(4 + 2, ' '); + if (suffix) ret.resize(4 + 2 + strlen(suffix), ' '); + return ret; + } + + const char* prefix[] = {"kB", "MB", "GB", "TB"}; + const int num_prefix = sizeof(prefix) / sizeof(const char*); + for (int i = 0; i < num_prefix; ++i) + { + val /= 1000.f; + if (std::fabs(val) < 1000.f) + { + ret = to_string(val, 4); + ret += prefix[i]; + if (suffix) ret += suffix; + return ret; + } + } + ret = to_string(val, 4); + ret += "PB"; + if (suffix) ret += suffix; + return ret; +} + +std::string color(std::string const& s, color_code c) +{ + if (c == col_none) return s; + + char buf[1024]; + snprintf(buf, sizeof(buf), "\x1b[3%dm%s\x1b[39m", c, s.c_str()); + return buf; +} + +std::string const& progress_bar(int progress, int width, color_code c + , char fill, char bg, std::string caption) +{ + static std::string bar; + bar.clear(); + bar.reserve(width + 10); + + int progress_chars = (progress * width + 500) / 1000; + + if (caption.empty()) + { + char code[10]; + snprintf(code, sizeof(code), "\x1b[3%dm", c); + bar = code; + std::fill_n(std::back_inserter(bar), progress_chars, fill); + std::fill_n(std::back_inserter(bar), width - progress_chars, bg); + bar += esc("39"); + } + else + { + // foreground color (depends a bit on background color) + color_code tc = col_black; + if (c == col_black || c == col_blue) + tc = col_white; + + caption.resize(width, ' '); + + char str[256]; + snprintf(str, sizeof(str), "\x1b[4%d;3%dm%s\x1b[48;5;238m\x1b[37m%s\x1b[49;39m" + , c, tc, caption.substr(0, progress_chars).c_str(), caption.substr(progress_chars).c_str()); + bar = str; + } + return bar; +} + +void set_cursor_pos(int x, int y) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + COORD c = {x, y}; + SetConsoleCursorPosition(out, c); +#else + printf("\033[%d;%dH", y + 1, x + 1); +#endif +} + +void clear_screen() +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + COORD c = {0, 0}; + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + DWORD n; + FillConsoleOutputCharacter(out, ' ', si.dwSize.X * si.dwSize.Y, c, &n); + FillConsoleOutputAttribute(out, 0x7, si.dwSize.X * si.dwSize.Y, c, &n); +#else + printf("\033[2J"); +#endif +} + +void clear_below(int y) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + COORD c = {0, y}; + SetConsoleCursorPosition(out, c); + CONSOLE_SCREEN_BUFFER_INFO si; + GetConsoleScreenBufferInfo(out, &si); + DWORD n; + FillConsoleOutputCharacter(out, ' ', si.dwSize.X * (si.dwSize.Y - y), c, &n); + FillConsoleOutputAttribute(out, 0x7, si.dwSize.X * (si.dwSize.Y - y), c, &n); +#else + printf("\033[%d;1H\033[J", y + 1); +#endif + +} + +void terminal_size(int* terminal_width, int* terminal_height) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO coninfo; + if (GetConsoleScreenBufferInfo(out, &coninfo)) + { + *terminal_width = coninfo.dwSize.X; + *terminal_height = coninfo.srWindow.Bottom - coninfo.srWindow.Top; +#else + int tty = open("/dev/tty", O_RDONLY); + winsize size; + int ret = ioctl(tty, TIOCGWINSZ, (char*)&size); + close(tty); + if (ret == 0) + { + *terminal_width = size.ws_col; + *terminal_height = size.ws_row; +#endif + + if (*terminal_width < 64) + *terminal_width = 64; + if (*terminal_height < 25) + *terminal_height = 25; + } + else + { + *terminal_width = 190; + *terminal_height = 100; + } +} + +#ifdef _WIN32 +void apply_ansi_code(int* attributes, bool* reverse, int code) +{ + const static int color_table[8] = + { + 0, // black + FOREGROUND_RED, // red + FOREGROUND_GREEN, // green + FOREGROUND_RED | FOREGROUND_GREEN, // yellow + FOREGROUND_BLUE, // blue + FOREGROUND_RED | FOREGROUND_BLUE, // magenta + FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE // white + }; + + enum + { + foreground_mask = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE, + background_mask = BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE + }; + + const static int fg_mask[2] = {foreground_mask, background_mask}; + const static int bg_mask[2] = {background_mask, foreground_mask}; + const static int fg_shift[2] = { 0, 4}; + const static int bg_shift[2] = { 4, 0}; + + if (code == 0) + { + // reset + *attributes = color_table[7]; + *reverse = false; + } + else if (code == 7) + { + if (*reverse) return; + *reverse = true; + int fg_col = *attributes & foreground_mask; + int bg_col = (*attributes & background_mask) >> 4; + *attributes &= ~(foreground_mask + background_mask); + *attributes |= fg_col << 4; + *attributes |= bg_col; + } + else if (code >= 30 && code <= 37) + { + // foreground color + *attributes &= ~fg_mask[*reverse]; + *attributes |= color_table[code - 30] << fg_shift[*reverse]; + } + else if (code >= 40 && code <= 47) + { + // foreground color + *attributes &= ~bg_mask[*reverse]; + *attributes |= color_table[code - 40] << bg_shift[*reverse]; + } +} +#endif +void print(char const* str) +{ +#ifdef _WIN32 + HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); + + char* buf = (char*)str; + + int current_attributes = 7; + bool reverse = false; + SetConsoleTextAttribute(out, current_attributes); + + char* start = buf; + DWORD written; + while (*buf != 0) + { + if (*buf == '\033' && buf[1] == '[') + { + *buf = 0; + WriteFile(out, start, buf - start, &written, NULL); + buf += 2; // skip escape and '[' + start = buf; + one_more: + while (*buf != 'm' && *buf != ';' && *buf != 0) ++buf; + if (*buf == 0) break; + int code = atoi(start); + apply_ansi_code(¤t_attributes, &reverse, code); + if (*buf == ';') + { + ++buf; + start = buf; + goto one_more; + } + SetConsoleTextAttribute(out, current_attributes); + ++buf; // skip 'm' + start = buf; + } + else + { + ++buf; + } + } + WriteFile(out, start, buf - start, &written, NULL); + +#else + puts(str); +#endif +} + diff --git a/examples/print.hpp b/examples/print.hpp new file mode 100644 index 000000000..46b6abf9b --- /dev/null +++ b/examples/print.hpp @@ -0,0 +1,41 @@ +#ifndef PRINT_HPP_ +#define PRINT_HPP_ + +#include + +enum color_code +{ + col_none = -1, + col_black = 0, + col_red = 1, + col_green = 2, + col_yellow = 3, + col_blue = 4, + col_magenta = 5, + col_cyan = 6, + col_white = 7, +}; + +char const* esc(char const* code); + +std::string to_string(int v, int width); + +std::string add_suffix(float val, char const* suffix = 0); + +std::string color(std::string const& s, color_code c); + +std::string const& progress_bar(int progress, int width, color_code c = col_green + , char fill = '#', char bg = '-', std::string caption = ""); + +void set_cursor_pos(int x, int y); + +void clear_screen(); + +void clear_below(int y); + +void terminal_size(int* terminal_width, int* terminal_height); + +void print(char const* str); + +#endif // PRINT_HPP_ + diff --git a/examples/torrent_view.cpp b/examples/torrent_view.cpp new file mode 100644 index 000000000..24182094c --- /dev/null +++ b/examples/torrent_view.cpp @@ -0,0 +1,386 @@ +#include "torrent_view.hpp" +#include "print.hpp" + +const int header_size = 2; + +std::string torrent_state(lt::torrent_status const& s) +{ + static char const* state_str[] = + {"checking (q)", "checking", "dl metadata" + , "downloading", "finished", "seeding", "allocating", "checking (r)"}; + + if (!s.error.empty()) return s.error; + std::string ret; + if (s.paused && !s.auto_managed) ret += "paused"; + else if (s.paused && s.auto_managed) ret += "queued"; + else if (s.upload_mode) ret += "upload mode"; + else ret += state_str[s.state]; + if (!s.paused && !s.auto_managed) ret += " [F]"; + char buf[10]; + snprintf(buf, sizeof(buf), " (%.1f%%)", s.progress_ppm / 10000.f); + ret += buf; + return ret; +} + +bool compare_torrent(lt::torrent_status const* lhs, lt::torrent_status const* rhs) +{ + if (lhs->queue_position != -1 && rhs->queue_position != -1) + { + // both are downloading, sort by queue pos + return lhs->queue_position < rhs->queue_position; + } + else if (lhs->queue_position == -1 && rhs->queue_position == -1) + { + // both are seeding, sort by seed-rank + if (lhs->seed_rank != rhs->seed_rank) + return lhs->seed_rank > rhs->seed_rank; + + return lhs->info_hash < rhs->info_hash; + } + + return (lhs->queue_position == -1) < (rhs->queue_position == -1); +} + +torrent_view::torrent_view() + : m_active_torrent(0) + , m_scroll_position(0) + , m_torrent_filter(0) + , m_max_lines(30) +{} + +void torrent_view::set_max_size(int height) +{ + m_max_lines = height; +} + +int torrent_view::filter() const +{ + return m_torrent_filter; +} + +void torrent_view::set_filter(int filter) +{ + if (filter == m_torrent_filter) return; + m_torrent_filter = filter; + + update_filtered_torrents(); + render(); +} + +// returns the lt::torrent_status of the currently selected torrent. +lt::torrent_status const& torrent_view::get_active_torrent() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + return *m_filtered_handles[m_active_torrent]; +} + +lt::torrent_handle torrent_view::get_active_handle() const +{ + if (m_active_torrent >= int(m_filtered_handles.size())) + m_active_torrent = int(m_filtered_handles.size()) - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + + if (m_filtered_handles.empty()) return lt::torrent_handle(); + + return m_filtered_handles[m_active_torrent]->handle; +} + +void torrent_view::update_torrents(std::vector const& st) +{ + std::set updates; + bool need_filter_update = false; + for (std::vector::const_iterator i = st.begin(); + i != st.end(); ++i) + { + boost::unordered_set::iterator j = m_all_handles.find(*i); + // add new entries here + if (j == m_all_handles.end()) + { + j = m_all_handles.insert(*i).first; + if (show_torrent(*j)) + { + m_filtered_handles.push_back(&*j); + need_filter_update = true; + } + } + else + { + bool prev_show = show_torrent(*j); + ((lt::torrent_status&)*j) = *i; + if (prev_show != show_torrent(*j)) + need_filter_update = true; + else + updates.insert(i->handle); + } + } + if (need_filter_update) + { + update_filtered_torrents(); + render(); + } + else + { + int torrent_index = 0; + int lines_printed = header_size; + for (std::vector::iterator i + = m_filtered_handles.begin(); + i != m_filtered_handles.end(); ++torrent_index) + { + if (torrent_index < m_scroll_position) + { + ++i; + continue; + } + if (lines_printed >= m_max_lines) + break; + + lt::torrent_status const& s = **i; + if (!s.handle.is_valid()) + continue; + ++i; + + if (updates.count(s.handle) == 0) + continue; + + set_cursor_pos(0, torrent_index + header_size); + print_torrent(s, torrent_index == m_active_torrent); + } + } +} + +int torrent_view::height() const +{ + return int(m_filtered_handles.size() + header_size); +} + +void torrent_view::arrow_up() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent <= 0) return; + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], false); + --m_active_torrent; + TORRENT_ASSERT(m_active_torrent >= 0); + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], true); +} + +void torrent_view::arrow_down() +{ + if (m_filtered_handles.empty()) return; + if (m_active_torrent >= int(m_filtered_handles.size()) - 1) return; + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], false); + + ++m_active_torrent; + TORRENT_ASSERT(m_active_torrent >= 0); + + set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); + print_torrent(*m_filtered_handles[m_active_torrent], true); +} + +void torrent_view::render() +{ + print_tabs(); + print_headers(); + + int lines_printed = header_size; + + // handle scrolling down when moving the cursor + // below the fold + TORRENT_ASSERT(m_scroll_position >= 0); + if (m_active_torrent >= m_max_lines - m_scroll_position) + m_scroll_position = m_active_torrent - m_max_lines + 1; + TORRENT_ASSERT(m_scroll_position >= 0); + if (m_active_torrent < m_scroll_position) + m_scroll_position = m_active_torrent; + TORRENT_ASSERT(m_scroll_position >= 0); + + int torrent_index = 0; + + for (std::vector::iterator i = m_filtered_handles.begin(); + i != m_filtered_handles.end(); ++torrent_index) + { + if (torrent_index < m_scroll_position) + { + ++i; + continue; + } + if (lines_printed >= m_max_lines) + { + print("...\n"); + ++lines_printed; + break; + } + + lt::torrent_status const& s = **i; + if (!s.handle.is_valid()) + { + i = m_filtered_handles.erase(i); + continue; + } + ++i; + + set_cursor_pos(0, torrent_index + header_size); + print_torrent(s, torrent_index == m_active_torrent); + ++lines_printed; + } + + clear_below(torrent_index + header_size); +} + +void torrent_view::print_tabs() +{ + set_cursor_pos(0, 0); + + char str[400]; + int pos = 0; + char const* filter_names[] = { "all", "downloading", "non-paused" + , "seeding", "queued", "stopped", "checking", "loaded"}; + for (int i = 0; i < int(sizeof(filter_names)/sizeof(filter_names[0])); ++i) + { + pos += snprintf(str+ pos, sizeof(str) - pos, "%s[%s]%s" + , m_torrent_filter == i?esc("7"):"" + , filter_names[i], m_torrent_filter == i?esc("0"):""); + } + pos += snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); + + print(str); +} + +void torrent_view::print_headers() +{ + set_cursor_pos(0, 1); + + char str[400]; + int pos = 0; + + // print title bar for torrent list + pos = snprintf(str, sizeof(str) + , " %-3s %-50s %-35s %-17s %-17s %-11s %-6s %-6s %-4s\x1bK" + , "#", "Name", "Progress", "Download", "Upload", "Peers (D:S)" + , "Down", "Up", "Flags"); + + print(str); +} + +void torrent_view::print_torrent(lt::torrent_status const& s, bool selected) +{ + int pos = 0; + char str[512]; + + // the active torrent is highligted in the list + // this inverses the forground and background colors + char const* selection = ""; + if (selected) + selection = "\x1b[1m\x1b[44m"; + + char queue_pos[16] = {0}; + if (s.queue_position == -1) + snprintf(queue_pos, sizeof(queue_pos), "-"); + else + snprintf(queue_pos, sizeof(queue_pos), "%d", s.queue_position); + + std::string name = s.name; + if (name.size() > 50) name.resize(50); + + color_code progress_bar_color = col_yellow; + if (!s.error.empty()) progress_bar_color = col_red; + else if (s.paused) progress_bar_color = col_blue; + else if (s.state == lt::torrent_status::downloading_metadata) + progress_bar_color = col_magenta; + else if (s.current_tracker.empty()) + progress_bar_color = col_green; + + pos += snprintf(str + pos, sizeof(str) - pos, "%s%c%-3s %-50s %s%s %s (%s) " + "%s (%s) %5d:%-5d %s %s %c%s" + , selection + , s.is_loaded ? 'L' : ' ' + , queue_pos + , name.c_str() + , progress_bar(s.progress_ppm / 1000, 35, progress_bar_color, '-', '#', torrent_state(s)).c_str() + , selection + , color(add_suffix(s.download_rate, "/s"), col_green).c_str() + , color(add_suffix(s.total_download), col_green).c_str() + , color(add_suffix(s.upload_rate, "/s"), col_red).c_str() + , color(add_suffix(s.total_upload), col_red).c_str() + , s.num_peers - s.num_seeds, s.num_seeds + , color(add_suffix(s.all_time_download), col_green).c_str() + , color(add_suffix(s.all_time_upload), col_red).c_str() + , s.need_save_resume?'S':' ', esc("0")); + + // if this is the selected torrent, restore the background color + if (selected) + pos += snprintf(str + pos, sizeof(str) - pos, "%s", esc("0")); + + pos += snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); +/* + // don't print the piece bar if we don't have any piece, or if we have all + if (print_piece_bar && s.num_pieces != 0 && s.progress_ppm != 1000000) + { + out += " "; + out += piece_bar(s.pieces, terminal_width - 7); + out += "\n"; + ++lines_printed; + if (s.seed_mode) + { + out += " "; + out += piece_bar(s.verified_pieces, terminal_width - 7); + out += "\n"; + ++lines_printed; + } + } +*/ + + print(str); +} + +bool torrent_view::show_torrent(lt::torrent_status const& st) +{ + switch (m_torrent_filter) + { + case torrents_all: return true; + case torrents_downloading: + return !st.paused + && st.state != lt::torrent_status::seeding + && st.state != lt::torrent_status::finished; + case torrents_not_paused: return !st.paused; + case torrents_seeding: + return !st.paused + && (st.state == lt::torrent_status::seeding + || st.state == lt::torrent_status::finished); + case torrents_queued: return st.paused && st.auto_managed; + case torrents_stopped: return st.paused && !st.auto_managed; + case torrents_checking: return st.state == lt::torrent_status::checking_files; + case torrents_loaded: return st.is_loaded; + } + return true; +} + +// refresh all pointers in m_filtered_handles. This must be done when +// inserting or removing elements from m_all_handles, since pointers may +// be invalidated or when a torrent changes status to either become +// visible or filtered +void torrent_view::update_filtered_torrents() +{ + m_filtered_handles.clear(); + for (boost::unordered_set::iterator i = m_all_handles.begin() + , end(m_all_handles.end()); i != end; ++i) + { + if (!show_torrent(*i)) continue; + m_filtered_handles.push_back(&*i); + } + if (m_active_torrent >= int(m_filtered_handles.size())) m_active_torrent = m_filtered_handles.size() - 1; + if (m_active_torrent < 0) m_active_torrent = 0; + TORRENT_ASSERT(m_active_torrent >= 0); + std::sort(m_filtered_handles.begin(), m_filtered_handles.end(), &compare_torrent); +} +