437 lines
12 KiB
C++
437 lines
12 KiB
C++
/*
|
|
|
|
Copyright (c) 2003-2017, Arvid Norberg
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in
|
|
the documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the author nor the names of its
|
|
contributors may be used to endorse or promote products derived
|
|
from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
*/
|
|
|
|
#include "torrent_view.hpp"
|
|
#include "print.hpp"
|
|
#include "libtorrent/add_torrent_params.hpp"
|
|
#include "libtorrent/torrent_handle.hpp"
|
|
#include "libtorrent/torrent_status.hpp"
|
|
|
|
const int header_size = 2;
|
|
using lt::queue_position_t;
|
|
|
|
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.flags & lt::torrent_flags::paused) &&
|
|
!(s.flags & lt::torrent_flags::auto_managed))
|
|
{
|
|
ret += "paused";
|
|
}
|
|
else if ((s.flags & lt::torrent_flags::paused) &&
|
|
(s.flags & lt::torrent_flags::auto_managed))
|
|
{
|
|
ret += "queued";
|
|
}
|
|
else if ((s.flags & lt::torrent_flags::upload_mode)) ret += "upload mode";
|
|
else ret += state_str[s.state];
|
|
if (!(s.flags & lt::torrent_flags::paused) &&
|
|
!(s.flags & lt::torrent_flags::auto_managed))
|
|
{
|
|
ret += " [F]";
|
|
}
|
|
ret += state_str[s.state];
|
|
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 != queue_position_t{-1} && rhs->queue_position != queue_position_t{-1})
|
|
{
|
|
// both are downloading, sort by queue pos
|
|
return lhs->queue_position < rhs->queue_position;
|
|
}
|
|
else if (lhs->queue_position == queue_position_t{-1}
|
|
&& rhs->queue_position == queue_position_t{-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 == queue_position_t{-1})
|
|
< (rhs->queue_position == queue_position_t{-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<lt::torrent_status> st)
|
|
{
|
|
std::set<lt::torrent_handle> updates;
|
|
bool need_filter_update = false;
|
|
for (lt::torrent_status& t : st)
|
|
{
|
|
auto j = m_all_handles.find(t);
|
|
// add new entries here
|
|
if (j == m_all_handles.end())
|
|
{
|
|
j = m_all_handles.insert(std::move(t)).first;
|
|
if (show_torrent(*j))
|
|
{
|
|
m_filtered_handles.push_back(&*j);
|
|
need_filter_update = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bool const prev_show = show_torrent(*j);
|
|
const_cast<lt::torrent_status&>(*j) = std::move(t);
|
|
if (prev_show != show_torrent(*j))
|
|
need_filter_update = true;
|
|
else
|
|
updates.insert(j->handle);
|
|
}
|
|
}
|
|
if (need_filter_update)
|
|
{
|
|
update_filtered_torrents();
|
|
render();
|
|
}
|
|
else
|
|
{
|
|
int torrent_index = 0;
|
|
for (auto i = m_filtered_handles.begin();
|
|
i != m_filtered_handles.end(); ++torrent_index, ++i)
|
|
{
|
|
if (torrent_index < m_scroll_position
|
|
|| torrent_index >= m_scroll_position + m_height - header_size)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
lt::torrent_status const& s = **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<lt::torrent_status const*>::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"};
|
|
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 == queue_position_t{-1})
|
|
std::snprintf(queue_pos, sizeof(queue_pos), "-");
|
|
else
|
|
std::snprintf(queue_pos, sizeof(queue_pos), "%d"
|
|
, static_cast<int>(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.flags & lt::torrent_flags::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%-3s %-50s %s%s %s (%s) "
|
|
"%s (%s) %5d:%-5d %s %s %c"
|
|
, selection
|
|
, 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.flags & lt::torrent_flags::paused)
|
|
&& st.state != lt::torrent_status::seeding
|
|
&& st.state != lt::torrent_status::finished;
|
|
case torrents_not_paused:
|
|
return !(st.flags & lt::torrent_flags::paused);
|
|
case torrents_seeding:
|
|
return !(st.flags & lt::torrent_flags::paused)
|
|
&& (st.state == lt::torrent_status::seeding
|
|
|| st.state == lt::torrent_status::finished);
|
|
case torrents_queued:
|
|
return (st.flags & lt::torrent_flags::paused)
|
|
&& (st.flags & lt::torrent_flags::auto_managed);
|
|
case torrents_stopped:
|
|
return (st.flags & lt::torrent_flags::paused)
|
|
&& !(st.flags & lt::torrent_flags::auto_managed);
|
|
case torrents_checking: return st.state == lt::torrent_status::checking_files;
|
|
}
|
|
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 (auto const& h : m_all_handles)
|
|
{
|
|
if (!show_torrent(h)) continue;
|
|
m_filtered_handles.push_back(&h);
|
|
}
|
|
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);
|
|
if (m_scroll_position + m_height - header_size > int(m_filtered_handles.size()))
|
|
{
|
|
m_scroll_position = std::max(0, int(m_filtered_handles.size()) - m_height + header_size);
|
|
}
|
|
}
|
|
|