#include "torrent_view.hpp" #include "print.hpp" #include "libtorrent/torrent_status.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.errc) return s.errc.message(); 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]; std::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_width(80) , m_height(30) {} void torrent_view::set_size(int width, int height) { if (m_width == width && m_height == height) return; m_width = width; m_height = height; render(); } 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; for (std::vector::iterator i = m_filtered_handles.begin(); i != m_filtered_handles.end(); ++torrent_index) { if (torrent_index < m_scroll_position || torrent_index >= m_scroll_position + m_height - header_size) { ++i; continue; } lt::torrent_status const& s = **i; ++i; if (!s.handle.is_valid()) continue; if (updates.count(s.handle) == 0) continue; set_cursor_pos(0, header_size + torrent_index - m_scroll_position); print_torrent(s, torrent_index == m_active_torrent); } } } int torrent_view::height() const { return m_height; } void torrent_view::arrow_up() { if (m_filtered_handles.empty()) return; if (m_active_torrent <= 0) return; if (m_active_torrent - 1 < m_scroll_position) { --m_active_torrent; m_scroll_position = m_active_torrent; TORRENT_ASSERT(m_scroll_position >= 0); render(); 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; int bottom_pos = m_height - header_size - 1; if (m_active_torrent - m_scroll_position + 1 > bottom_pos) { ++m_active_torrent; m_scroll_position = m_active_torrent - bottom_pos; TORRENT_ASSERT(m_scroll_position >= 0); render(); return; } set_cursor_pos(0, header_size + m_active_torrent - m_scroll_position); print_torrent(*m_filtered_handles[m_active_torrent], false); TORRENT_ASSERT(m_active_torrent >= 0); ++m_active_torrent; 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; 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_height) 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 - m_scroll_position); print_torrent(s, torrent_index == m_active_torrent); ++lines_printed; } clear_rows(torrent_index + header_size, m_height); } 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 += std::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 += std::snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); if (m_width + 1 < int(sizeof(str))) str[m_width + 1] = '\0'; print(str); } void torrent_view::print_headers() { set_cursor_pos(0, 1); char str[400]; // print title bar for torrent list std::snprintf(str, sizeof(str) , " %-3s %-50s %-35s %-17s %-17s %-11s %-6s %-6s %-4s\x1b[K" , "#", "Name", "Progress", "Download", "Upload", "Peers (D:S)" , "Down", "Up", "Flags"); if (m_width + 1 < int(sizeof(str))) str[m_width + 1] = '\0'; 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) std::snprintf(queue_pos, sizeof(queue_pos), "-"); else std::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.errc) 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 += std::snprintf(str + pos, sizeof(str) - pos, "%s%c%-3s %-50s %s%s %s (%s) " "%s (%s) %5d:%-5d %s %s %c" , 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':' '); // if this is the selected torrent, restore the background color if (selected) pos += std::snprintf(str + pos, sizeof(str) - pos, "%s", esc("0")); pos += std::snprintf(str + pos, sizeof(str) - pos, "\x1b[K"); 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_scroll_position = 0; 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 = int(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); }