overhauled client_test. refactored and simplified. still in progress

This commit is contained in:
Arvid Norberg 2014-07-08 15:35:14 +00:00
parent a6f345181f
commit a63370f650
5 changed files with 817 additions and 756 deletions

View File

@ -16,7 +16,7 @@ project client_test
<link>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 ;

File diff suppressed because it is too large Load Diff

298
examples/print.cpp Normal file
View File

@ -0,0 +1,298 @@
#ifdef _WIN32
#include <windows.h>
#include <conio.h>
#else
#include <unistd.h> // for close()
#include <fcntl.h> // for open()
#include <sys/ioctl.h>
#endif
#include "print.hpp"
#include <cmath>
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(&current_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
}

41
examples/print.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef PRINT_HPP_
#define PRINT_HPP_
#include <string>
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_

386
examples/torrent_view.cpp Normal file
View File

@ -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<lt::torrent_status> const& st)
{
std::set<lt::torrent_handle> updates;
bool need_filter_update = false;
for (std::vector<lt::torrent_status>::const_iterator i = st.begin();
i != st.end(); ++i)
{
boost::unordered_set<lt::torrent_status>::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<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_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<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_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<lt::torrent_status>::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);
}