experimental support for filtering pieces (filter from downloading that is)
This commit is contained in:
parent
9707b6aeb0
commit
2fabb2bf80
|
@ -193,7 +193,7 @@ Boost.Filesystem, Boost.Date_time and various other boost libraries as well as z
|
||||||
<ul class="simple">
|
<ul class="simple">
|
||||||
<li>Windows 2000 vc7.1</li>
|
<li>Windows 2000 vc7.1</li>
|
||||||
<li>Linux x86 GCC 3.0.4, GCC 3.2.3, GCC 3.4.2</li>
|
<li>Linux x86 GCC 3.0.4, GCC 3.2.3, GCC 3.4.2</li>
|
||||||
<li>MacOS X, GCC 3.3</li>
|
<li>MacOS X, (Apple's) GCC 3.3, (Apple's) GCC 4.0</li>
|
||||||
<li>SunOS 5.8 GCC 3.1</li>
|
<li>SunOS 5.8 GCC 3.1</li>
|
||||||
<li>Cygwin GCC 3.3.3</li>
|
<li>Cygwin GCC 3.3.3</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -887,6 +887,10 @@ struct torrent_handle
|
||||||
bool is_paused() const;
|
bool is_paused() const;
|
||||||
bool is_seed() const;
|
bool is_seed() const;
|
||||||
|
|
||||||
|
void filter_piece(int index, bool filter);
|
||||||
|
bool is_piece_filtered(int index) const;
|
||||||
|
std::vector<bool> filtered_pieces() const;
|
||||||
|
|
||||||
int num_complete() const;
|
int num_complete() const;
|
||||||
int num_incomplete() const;
|
int num_incomplete() const;
|
||||||
|
|
||||||
|
@ -905,8 +909,9 @@ struct torrent_handle
|
||||||
<p>The default constructor will initialize the handle to an invalid state. Which means you cannot
|
<p>The default constructor will initialize the handle to an invalid state. Which means you cannot
|
||||||
perform any operation on it, unless you first assign it a valid handle. If you try to perform
|
perform any operation on it, unless you first assign it a valid handle. If you try to perform
|
||||||
any operation on an uninitialized handle, it will throw <tt class="docutils literal"><span class="pre">invalid_handle</span></tt>.</p>
|
any operation on an uninitialized handle, it will throw <tt class="docutils literal"><span class="pre">invalid_handle</span></tt>.</p>
|
||||||
<p><strong>TODO: document trackers() and replace_trackers()</strong>
|
<p><strong>TODO: document trackers() and replace_trackers()</strong></p>
|
||||||
<strong>TODO: document how to create a .torrent</strong></p>
|
<p><strong>TODO: document how to create a .torrent</strong></p>
|
||||||
|
<p><strong>TODO: document filter_piece(), is_piece_filtered() and filtered_pieces()</strong></p>
|
||||||
<div class="section" id="save-path">
|
<div class="section" id="save-path">
|
||||||
<h2><a name="save-path">save_path()</a></h2>
|
<h2><a name="save-path">save_path()</a></h2>
|
||||||
<blockquote>
|
<blockquote>
|
||||||
|
@ -1340,6 +1345,8 @@ struct peer_info
|
||||||
address ip;
|
address ip;
|
||||||
float up_speed;
|
float up_speed;
|
||||||
float down_speed;
|
float down_speed;
|
||||||
|
float payload_up_speed;
|
||||||
|
float payload_down_speed;
|
||||||
size_type total_download;
|
size_type total_download;
|
||||||
size_type total_upload;
|
size_type total_upload;
|
||||||
peer_id id;
|
peer_id id;
|
||||||
|
@ -1393,8 +1400,10 @@ us.</td>
|
||||||
</table>
|
</table>
|
||||||
<p>The <tt class="docutils literal"><span class="pre">ip</span></tt> field is the IP-address to this peer. Its type is a wrapper around the
|
<p>The <tt class="docutils literal"><span class="pre">ip</span></tt> field is the IP-address to this peer. Its type is a wrapper around the
|
||||||
actual address and the port number. See <a class="reference" href="#address">address</a> class.</p>
|
actual address and the port number. See <a class="reference" href="#address">address</a> class.</p>
|
||||||
<p><tt class="docutils literal"><span class="pre">up_speed</span></tt> and <tt class="docutils literal"><span class="pre">down_speed</span></tt> is the current upload and download speed
|
<p><tt class="docutils literal"><span class="pre">up_speed</span></tt> and <tt class="docutils literal"><span class="pre">down_speed</span></tt> contains the current upload and download speed
|
||||||
we have to and from this peer. These figures are updated aproximately once every second.</p>
|
we have to and from this peer (including any protocol messages). The transfer rates
|
||||||
|
of payload data only are found in <tt class="docutils literal"><span class="pre">payload_up_speed</span></tt> and <tt class="docutils literal"><span class="pre">payload_down_speed</span></tt>.
|
||||||
|
These figures are updated aproximately once every second.</p>
|
||||||
<p><tt class="docutils literal"><span class="pre">total_download</span></tt> and <tt class="docutils literal"><span class="pre">total_upload</span></tt> are the total number of bytes downloaded
|
<p><tt class="docutils literal"><span class="pre">total_download</span></tt> and <tt class="docutils literal"><span class="pre">total_upload</span></tt> are the total number of bytes downloaded
|
||||||
from and uploaded to this peer. These numbers do not include the protocol chatter, but only
|
from and uploaded to this peer. These numbers do not include the protocol chatter, but only
|
||||||
the payload data.</p>
|
the payload data.</p>
|
||||||
|
|
|
@ -70,7 +70,7 @@ libtorrent has been successfully compiled and tested on:
|
||||||
|
|
||||||
* Windows 2000 vc7.1
|
* Windows 2000 vc7.1
|
||||||
* Linux x86 GCC 3.0.4, GCC 3.2.3, GCC 3.4.2
|
* Linux x86 GCC 3.0.4, GCC 3.2.3, GCC 3.4.2
|
||||||
* MacOS X, GCC 3.3
|
* MacOS X, (Apple's) GCC 3.3, (Apple's) GCC 4.0
|
||||||
* SunOS 5.8 GCC 3.1
|
* SunOS 5.8 GCC 3.1
|
||||||
* Cygwin GCC 3.3.3
|
* Cygwin GCC 3.3.3
|
||||||
|
|
||||||
|
@ -816,6 +816,10 @@ Its declaration looks like this::
|
||||||
bool is_paused() const;
|
bool is_paused() const;
|
||||||
bool is_seed() const;
|
bool is_seed() const;
|
||||||
|
|
||||||
|
void filter_piece(int index, bool filter);
|
||||||
|
bool is_piece_filtered(int index) const;
|
||||||
|
std::vector<bool> filtered_pieces() const;
|
||||||
|
|
||||||
int num_complete() const;
|
int num_complete() const;
|
||||||
int num_incomplete() const;
|
int num_incomplete() const;
|
||||||
|
|
||||||
|
@ -836,8 +840,11 @@ perform any operation on it, unless you first assign it a valid handle. If you t
|
||||||
any operation on an uninitialized handle, it will throw ``invalid_handle``.
|
any operation on an uninitialized handle, it will throw ``invalid_handle``.
|
||||||
|
|
||||||
**TODO: document trackers() and replace_trackers()**
|
**TODO: document trackers() and replace_trackers()**
|
||||||
|
|
||||||
**TODO: document how to create a .torrent**
|
**TODO: document how to create a .torrent**
|
||||||
|
|
||||||
|
**TODO: document filter_piece(), is_piece_filtered() and filtered_pieces()**
|
||||||
|
|
||||||
save_path()
|
save_path()
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
@ -1301,6 +1308,8 @@ It contains the following fields::
|
||||||
address ip;
|
address ip;
|
||||||
float up_speed;
|
float up_speed;
|
||||||
float down_speed;
|
float down_speed;
|
||||||
|
float payload_up_speed;
|
||||||
|
float payload_down_speed;
|
||||||
size_type total_download;
|
size_type total_download;
|
||||||
size_type total_upload;
|
size_type total_upload;
|
||||||
peer_id id;
|
peer_id id;
|
||||||
|
@ -1346,8 +1355,10 @@ __ http://nolar.com/azureus/extended.htm
|
||||||
The ``ip`` field is the IP-address to this peer. Its type is a wrapper around the
|
The ``ip`` field is the IP-address to this peer. Its type is a wrapper around the
|
||||||
actual address and the port number. See address_ class.
|
actual address and the port number. See address_ class.
|
||||||
|
|
||||||
``up_speed`` and ``down_speed`` is the current upload and download speed
|
``up_speed`` and ``down_speed`` contains the current upload and download speed
|
||||||
we have to and from this peer. These figures are updated aproximately once every second.
|
we have to and from this peer (including any protocol messages). The transfer rates
|
||||||
|
of payload data only are found in ``payload_up_speed`` and ``payload_down_speed``.
|
||||||
|
These figures are updated aproximately once every second.
|
||||||
|
|
||||||
``total_download`` and ``total_upload`` are the total number of bytes downloaded
|
``total_download`` and ``total_upload`` are the total number of bytes downloaded
|
||||||
from and uploaded to this peer. These numbers do not include the protocol chatter, but only
|
from and uploaded to this peer. These numbers do not include the protocol chatter, but only
|
||||||
|
|
|
@ -147,10 +147,10 @@ void clear()
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string to_string(float v, int width)
|
std::string to_string(float v, int width, int precision = 4)
|
||||||
{
|
{
|
||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
s.precision(width-2);
|
s.precision(precision);
|
||||||
s.flags(std::ios_base::right);
|
s.flags(std::ios_base::right);
|
||||||
s.width(width);
|
s.width(width);
|
||||||
s.fill(' ');
|
s.fill(' ');
|
||||||
|
@ -158,10 +158,10 @@ std::string to_string(float v, int width)
|
||||||
return s.str();
|
return s.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string pos_to_string(float v, int width)
|
std::string pos_to_string(float v, int width, int precision = 4)
|
||||||
{
|
{
|
||||||
std::stringstream s;
|
std::stringstream s;
|
||||||
s.precision(width-1);
|
s.precision(precision);
|
||||||
s.flags(std::ios_base::right);
|
s.flags(std::ios_base::right);
|
||||||
s.width(width);
|
s.width(width);
|
||||||
s.fill(' ');
|
s.fill(' ');
|
||||||
|
@ -194,12 +194,11 @@ std::string add_suffix(float val)
|
||||||
{
|
{
|
||||||
const char* prefix[] = {"B", "kB", "MB", "GB", "TB"};
|
const char* prefix[] = {"B", "kB", "MB", "GB", "TB"};
|
||||||
const int num_prefix = sizeof(prefix) / sizeof(const char*);
|
const int num_prefix = sizeof(prefix) / sizeof(const char*);
|
||||||
int i;
|
for (int i = 0; i < num_prefix; ++i)
|
||||||
for (i = 0; i < num_prefix; ++i)
|
|
||||||
{
|
{
|
||||||
if (fabs(val) < 1000.f)
|
if (fabs(val) < 1000.f)
|
||||||
return to_string(val, i==0?7:6) + prefix[i];
|
return to_string(val, i==0?7:6) + prefix[i];
|
||||||
val /= 1024.f;
|
val /= 1000.f;
|
||||||
}
|
}
|
||||||
return to_string(val, 6) + "PB";
|
return to_string(val, 6) + "PB";
|
||||||
}
|
}
|
||||||
|
@ -221,8 +220,7 @@ void print_peer_info(std::ostream& out, std::vector<libtorrent::peer_info> const
|
||||||
out << " down up q r flags client block\n";
|
out << " down up q r flags client block\n";
|
||||||
|
|
||||||
for (std::vector<peer_info>::const_iterator i = peers.begin();
|
for (std::vector<peer_info>::const_iterator i = peers.begin();
|
||||||
i != peers.end();
|
i != peers.end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
out.fill(' ');
|
out.fill(' ');
|
||||||
out.width(2);
|
out.width(2);
|
||||||
|
@ -233,8 +231,8 @@ void print_peer_info(std::ostream& out, std::vector<libtorrent::peer_info> const
|
||||||
// << "ul:" << add_suffix(i->upload_limit) << "/s "
|
// << "ul:" << add_suffix(i->upload_limit) << "/s "
|
||||||
// << "uc:" << add_suffix(i->upload_ceiling) << "/s "
|
// << "uc:" << add_suffix(i->upload_ceiling) << "/s "
|
||||||
// << "df:" << ratio(i->total_download, i->total_upload) << " "
|
// << "df:" << ratio(i->total_download, i->total_upload) << " "
|
||||||
<< to_string(i->download_queue_length, 2) << " "
|
<< to_string(i->download_queue_length, 2, 2) << " "
|
||||||
<< to_string(i->upload_queue_length, 2) << " "
|
<< to_string(i->upload_queue_length, 2, 2) << " "
|
||||||
<< static_cast<const char*>((i->flags & peer_info::interesting)?"I":"_")
|
<< static_cast<const char*>((i->flags & peer_info::interesting)?"I":"_")
|
||||||
<< static_cast<const char*>((i->flags & peer_info::choked)?"C":"_")
|
<< static_cast<const char*>((i->flags & peer_info::choked)?"C":"_")
|
||||||
<< static_cast<const char*>((i->flags & peer_info::remote_interested)?"i":"_")
|
<< static_cast<const char*>((i->flags & peer_info::remote_interested)?"i":"_")
|
||||||
|
@ -354,8 +352,7 @@ int main(int argc, char* argv[])
|
||||||
if (c == 'q')
|
if (c == 'q')
|
||||||
{
|
{
|
||||||
for (std::vector<torrent_handle>::iterator i = handles.begin();
|
for (std::vector<torrent_handle>::iterator i = handles.begin();
|
||||||
i != handles.end();
|
i != handles.end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
torrent_handle h = *i;
|
torrent_handle h = *i;
|
||||||
if (!h.get_torrent_info().is_valid()) continue;
|
if (!h.get_torrent_info().is_valid()) continue;
|
||||||
|
@ -441,8 +438,7 @@ int main(int argc, char* argv[])
|
||||||
|
|
||||||
std::stringstream out;
|
std::stringstream out;
|
||||||
for (std::vector<torrent_handle>::iterator i = handles.begin();
|
for (std::vector<torrent_handle>::iterator i = handles.begin();
|
||||||
i != handles.end();
|
i != handles.end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
if (!i->is_valid())
|
if (!i->is_valid())
|
||||||
{
|
{
|
||||||
|
@ -515,14 +511,14 @@ int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
out.width(4);
|
out.width(4);
|
||||||
out.fill(' ');
|
out.fill(' ');
|
||||||
out << i->piece_index << ": |";
|
out << i->piece_index << ": [";
|
||||||
for (int j = 0; j < i->blocks_in_piece; ++j)
|
for (int j = 0; j < i->blocks_in_piece; ++j)
|
||||||
{
|
{
|
||||||
if (i->finished_blocks[j]) out << "#";
|
if (i->finished_blocks[j]) out << "#";
|
||||||
else if (i->requested_blocks[j]) out << "+";
|
else if (i->requested_blocks[j]) out << "+";
|
||||||
else out << ".";
|
else out << "-";
|
||||||
}
|
}
|
||||||
out << "|\n";
|
out << "]\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
out << "___________________________________\n";
|
out << "___________________________________\n";
|
||||||
|
|
|
@ -102,11 +102,7 @@ namespace libtorrent
|
||||||
void write_string(OutIt& out, const std::string& val)
|
void write_string(OutIt& out, const std::string& val)
|
||||||
{
|
{
|
||||||
std::string::const_iterator end = val.begin() + val.length();
|
std::string::const_iterator end = val.begin() + val.length();
|
||||||
for (std::string::const_iterator i = val.begin(); i != end; ++i)
|
std::copy(val.begin(), end, out);
|
||||||
{
|
|
||||||
*out = *i;
|
|
||||||
++out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class OutIt>
|
template <class OutIt>
|
||||||
|
@ -115,6 +111,13 @@ namespace libtorrent
|
||||||
write_string(out, boost::lexical_cast<std::string>(val));
|
write_string(out, boost::lexical_cast<std::string>(val));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <class OutIt>
|
||||||
|
void write_char(OutIt& out, char c)
|
||||||
|
{
|
||||||
|
*out = c;
|
||||||
|
++out;
|
||||||
|
}
|
||||||
|
|
||||||
template <class InIt>
|
template <class InIt>
|
||||||
std::string read_until(InIt& in, InIt end, char end_token)
|
std::string read_until(InIt& in, InIt end, char end_token)
|
||||||
{
|
{
|
||||||
|
@ -149,35 +152,34 @@ namespace libtorrent
|
||||||
switch(e.type())
|
switch(e.type())
|
||||||
{
|
{
|
||||||
case entry::int_t:
|
case entry::int_t:
|
||||||
*out = 'i'; ++out;
|
write_char(out, 'i');
|
||||||
write_integer(out, e.integer());
|
write_integer(out, e.integer());
|
||||||
*out = 'e'; ++out;
|
write_char(out, 'e');
|
||||||
break;
|
break;
|
||||||
case entry::string_t:
|
case entry::string_t:
|
||||||
write_integer(out, e.string().length());
|
write_integer(out, e.string().length());
|
||||||
*out = ':'; ++out;
|
write_char(out, ':');
|
||||||
write_string(out, e.string());
|
write_string(out, e.string());
|
||||||
break;
|
break;
|
||||||
case entry::list_t:
|
case entry::list_t:
|
||||||
*out = 'l'; ++out;
|
write_char(out, 'l');
|
||||||
for (entry::list_type::const_iterator i = e.list().begin(); i != e.list().end(); ++i)
|
for (entry::list_type::const_iterator i = e.list().begin(); i != e.list().end(); ++i)
|
||||||
bencode_recursive(out, *i);
|
bencode_recursive(out, *i);
|
||||||
*out = 'e'; ++out;
|
write_char(out, 'e');
|
||||||
break;
|
break;
|
||||||
case entry::dictionary_t:
|
case entry::dictionary_t:
|
||||||
*out = 'd'; ++out;
|
write_char(out, 'd');
|
||||||
for (entry::dictionary_type::const_iterator i = e.dict().begin();
|
for (entry::dictionary_type::const_iterator i = e.dict().begin();
|
||||||
i != e.dict().end();
|
i != e.dict().end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
// write key
|
// write key
|
||||||
write_integer(out, i->first.length());
|
write_integer(out, i->first.length());
|
||||||
*out = ':'; ++out;
|
write_char(out, ':');
|
||||||
write_string(out, i->first);
|
write_string(out, i->first);
|
||||||
// write value
|
// write value
|
||||||
bencode_recursive(out, i->second);
|
bencode_recursive(out, i->second);
|
||||||
}
|
}
|
||||||
*out = 'e'; ++out;
|
write_char(out, 'e');
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw invalid_encoding();
|
throw invalid_encoding();
|
||||||
|
|
|
@ -57,7 +57,6 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
#include "libtorrent/socket.hpp"
|
#include "libtorrent/socket.hpp"
|
||||||
#include "libtorrent/peer_id.hpp"
|
#include "libtorrent/peer_id.hpp"
|
||||||
#include "libtorrent/storage.hpp"
|
#include "libtorrent/storage.hpp"
|
||||||
#include "libtorrent/piece_picker.hpp"
|
|
||||||
#include "libtorrent/stat.hpp"
|
#include "libtorrent/stat.hpp"
|
||||||
#include "libtorrent/debug.hpp"
|
#include "libtorrent/debug.hpp"
|
||||||
#include "libtorrent/alert.hpp"
|
#include "libtorrent/alert.hpp"
|
||||||
|
|
|
@ -56,6 +56,8 @@ namespace libtorrent
|
||||||
address ip;
|
address ip;
|
||||||
float up_speed;
|
float up_speed;
|
||||||
float down_speed;
|
float down_speed;
|
||||||
|
float payload_up_speed;
|
||||||
|
float payload_down_speed;
|
||||||
size_type total_download;
|
size_type total_download;
|
||||||
size_type total_upload;
|
size_type total_upload;
|
||||||
peer_id id;
|
peer_id id;
|
||||||
|
|
|
@ -134,6 +134,23 @@ namespace libtorrent
|
||||||
// (i.e. we don't have to maintain a refcount)
|
// (i.e. we don't have to maintain a refcount)
|
||||||
void we_have(int index);
|
void we_have(int index);
|
||||||
|
|
||||||
|
// This will mark a piece as unfiltered, and if it was
|
||||||
|
// previously marked as filtered, it will be considered
|
||||||
|
// interesting again and be placed in the piece list available
|
||||||
|
// for downloading.
|
||||||
|
void mark_as_unfiltered(int index);
|
||||||
|
|
||||||
|
// This will mark a piece as filtered. The piece will be
|
||||||
|
// removed from the list of pieces avalable for downloading
|
||||||
|
// and hence, will not be downloaded.
|
||||||
|
void mark_as_filtered(int index);
|
||||||
|
|
||||||
|
// returns true if the pieces at 'index' is marked as filtered
|
||||||
|
bool is_filtered(int index) const;
|
||||||
|
|
||||||
|
// fills the bitmask with 1's for pieces that are filtered
|
||||||
|
void filtered_pieces(std::vector<bool>& mask) const;
|
||||||
|
|
||||||
// pieces should be the vector that represents the pieces a
|
// pieces should be the vector that represents the pieces a
|
||||||
// client has. It returns a list of all pieces that this client
|
// client has. It returns a list of all pieces that this client
|
||||||
// has and that are interesting to download. It returns them in
|
// has and that are interesting to download. It returns them in
|
||||||
|
@ -209,6 +226,7 @@ namespace libtorrent
|
||||||
piece_pos(int peer_count_, int index_)
|
piece_pos(int peer_count_, int index_)
|
||||||
: peer_count(peer_count_)
|
: peer_count(peer_count_)
|
||||||
, downloading(0)
|
, downloading(0)
|
||||||
|
, filtered(0)
|
||||||
, index(index_)
|
, index(index_)
|
||||||
{
|
{
|
||||||
assert(peer_count_ >= 0);
|
assert(peer_count_ >= 0);
|
||||||
|
@ -219,8 +237,12 @@ namespace libtorrent
|
||||||
unsigned peer_count : 11;
|
unsigned peer_count : 11;
|
||||||
// is 1 if the piece is marked as being downloaded
|
// is 1 if the piece is marked as being downloaded
|
||||||
unsigned downloading : 1;
|
unsigned downloading : 1;
|
||||||
|
// is 1 if the piece is filtered (not to be downloaded)
|
||||||
|
unsigned filtered : 1;
|
||||||
// index in to the piece_info vector
|
// index in to the piece_info vector
|
||||||
unsigned index : 20;
|
unsigned index : 19;
|
||||||
|
|
||||||
|
enum { we_have_index = 0x3ffff };
|
||||||
|
|
||||||
bool operator!=(piece_pos p)
|
bool operator!=(piece_pos p)
|
||||||
{ return index != p.index || peer_count != p.peer_count; }
|
{ return index != p.index || peer_count != p.peer_count; }
|
||||||
|
@ -231,8 +253,13 @@ namespace libtorrent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
void move(bool downloading, int vec_index, int elem_index);
|
void move(bool downloading, bool filtered, int vec_index, int elem_index);
|
||||||
void remove(bool downloading, int vec_index, int elem_index);
|
void remove(bool downloading, bool filtered, int vec_index, int elem_index);
|
||||||
|
std::vector<std::vector<int> >& pick_piece_info_vector(bool downloading
|
||||||
|
, bool filtered);
|
||||||
|
|
||||||
|
std::vector<std::vector<int> > const& pick_piece_info_vector(
|
||||||
|
bool downloading, bool filtered) const;
|
||||||
|
|
||||||
int add_interesting_blocks(const std::vector<int>& piece_list,
|
int add_interesting_blocks(const std::vector<int>& piece_list,
|
||||||
const std::vector<bool>& pieces,
|
const std::vector<bool>& pieces,
|
||||||
|
@ -252,10 +279,14 @@ namespace libtorrent
|
||||||
// during piece picking
|
// during piece picking
|
||||||
std::vector<std::vector<int> > m_downloading_piece_info;
|
std::vector<std::vector<int> > m_downloading_piece_info;
|
||||||
|
|
||||||
|
// this vector has the same structure as m_piece_info
|
||||||
|
// but only contains pieces we aren't interested in (filtered)
|
||||||
|
std::vector<std::vector<int> > m_filtered_piece_info;
|
||||||
|
|
||||||
// this maps indices to number of peers that has this piece and
|
// this maps indices to number of peers that has this piece and
|
||||||
// index into the m_piece_info vectors.
|
// index into the m_piece_info vectors.
|
||||||
// 0xfffff means that we have the piece, so it doesn't
|
// piece_pos::we_have_index means that we have the piece, so it
|
||||||
// exist in the piece_info buckets
|
// doesn't exist in the piece_info buckets
|
||||||
std::vector<piece_pos> m_piece_map;
|
std::vector<piece_pos> m_piece_map;
|
||||||
|
|
||||||
// each piece that's currently being downloaded
|
// each piece that's currently being downloaded
|
||||||
|
|
|
@ -141,6 +141,10 @@ namespace libtorrent
|
||||||
void resume();
|
void resume();
|
||||||
bool is_paused() const { return m_paused; }
|
bool is_paused() const { return m_paused; }
|
||||||
|
|
||||||
|
void filter_piece(int index, bool download);
|
||||||
|
bool is_piece_filtered(int index) const;
|
||||||
|
void filtered_pieces(std::vector<bool>& bitmask) const;
|
||||||
|
|
||||||
torrent_status status() const;
|
torrent_status status() const;
|
||||||
|
|
||||||
void use_interface(const char* net_interface);
|
void use_interface(const char* net_interface);
|
||||||
|
|
|
@ -208,6 +208,13 @@ namespace libtorrent
|
||||||
void pause();
|
void pause();
|
||||||
void resume();
|
void resume();
|
||||||
|
|
||||||
|
|
||||||
|
// marks the piece with the given index as filtered
|
||||||
|
// it will not be downloaded
|
||||||
|
void filter_piece(int index, bool filter);
|
||||||
|
bool is_piece_filtered(int index) const;
|
||||||
|
std::vector<bool> filtered_pieces() const;
|
||||||
|
|
||||||
// set the interface to bind outgoing connections
|
// set the interface to bind outgoing connections
|
||||||
// to.
|
// to.
|
||||||
void use_interface(const char* net_interface);
|
void use_interface(const char* net_interface);
|
||||||
|
|
|
@ -273,7 +273,8 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
int index = *i;
|
int index = *i;
|
||||||
m_torrent->peer_has(index);
|
m_torrent->peer_has(index);
|
||||||
if (!m_torrent->have_piece(index))
|
if (!m_torrent->have_piece(index)
|
||||||
|
&& m_torrent->picker().is_filtered(index))
|
||||||
interesting = true;
|
interesting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -654,7 +655,9 @@ namespace libtorrent
|
||||||
++m_num_pieces;
|
++m_num_pieces;
|
||||||
m_torrent->peer_has(index);
|
m_torrent->peer_has(index);
|
||||||
|
|
||||||
if (!m_torrent->have_piece(index) && !is_interesting())
|
if (!m_torrent->have_piece(index)
|
||||||
|
&& !is_interesting()
|
||||||
|
&& !m_torrent->picker().is_filtered(index))
|
||||||
m_torrent->get_policy().peer_is_interesting(*this);
|
m_torrent->get_policy().peer_is_interesting(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -729,12 +732,12 @@ namespace libtorrent
|
||||||
// peer has, in a shuffled order
|
// peer has, in a shuffled order
|
||||||
bool interesting = false;
|
bool interesting = false;
|
||||||
for (std::vector<int>::iterator i = piece_list.begin();
|
for (std::vector<int>::iterator i = piece_list.begin();
|
||||||
i != piece_list.end();
|
i != piece_list.end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
int index = *i;
|
int index = *i;
|
||||||
m_torrent->peer_has(index);
|
m_torrent->peer_has(index);
|
||||||
if (!m_torrent->have_piece(index))
|
if (!m_torrent->have_piece(index)
|
||||||
|
&& !m_torrent->picker().is_filtered(index))
|
||||||
interesting = true;
|
interesting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1571,7 +1574,14 @@ namespace libtorrent
|
||||||
#ifdef TORRENT_VERBOSE_LOGGING
|
#ifdef TORRENT_VERBOSE_LOGGING
|
||||||
using namespace boost::posix_time;
|
using namespace boost::posix_time;
|
||||||
(*m_logger) << to_simple_string(second_clock::universal_time())
|
(*m_logger) << to_simple_string(second_clock::universal_time())
|
||||||
<< " ==> BITFIELD\n";
|
<< " ==> BITFIELD ";
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)m_have_piece.size(); ++i)
|
||||||
|
{
|
||||||
|
if (m_torrent->have_piece(i)) (*m_logger) << "1";
|
||||||
|
else (*m_logger) << "0";
|
||||||
|
}
|
||||||
|
(*m_logger) << "\n";
|
||||||
#endif
|
#endif
|
||||||
const int packet_size = ((int)m_have_piece.size() + 7) / 8 + 5;
|
const int packet_size = ((int)m_have_piece.size() + 7) / 8 + 5;
|
||||||
const int old_size = (int)m_send_buffer.size();
|
const int old_size = (int)m_send_buffer.size();
|
||||||
|
|
|
@ -57,6 +57,7 @@ namespace libtorrent
|
||||||
piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks)
|
piece_picker::piece_picker(int blocks_per_piece, int total_num_blocks)
|
||||||
: m_piece_info(2)
|
: m_piece_info(2)
|
||||||
, m_downloading_piece_info(2)
|
, m_downloading_piece_info(2)
|
||||||
|
, m_filtered_piece_info(2)
|
||||||
, m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece)
|
, m_piece_map((total_num_blocks + blocks_per_piece-1) / blocks_per_piece)
|
||||||
{
|
{
|
||||||
assert(blocks_per_piece > 0);
|
assert(blocks_per_piece > 0);
|
||||||
|
@ -64,7 +65,7 @@ namespace libtorrent
|
||||||
|
|
||||||
// the piece index is stored in 20 bits, which limits the allowed
|
// the piece index is stored in 20 bits, which limits the allowed
|
||||||
// number of pieces somewhat
|
// number of pieces somewhat
|
||||||
if (m_piece_map.size() >= 0xfffff) throw std::runtime_error("too many pieces in torrent");
|
if (m_piece_map.size() >= piece_pos::we_have_index) throw std::runtime_error("too many pieces in torrent");
|
||||||
|
|
||||||
m_blocks_per_piece = blocks_per_piece;
|
m_blocks_per_piece = blocks_per_piece;
|
||||||
m_blocks_in_last_piece = total_num_blocks % blocks_per_piece;
|
m_blocks_in_last_piece = total_num_blocks % blocks_per_piece;
|
||||||
|
@ -76,7 +77,7 @@ namespace libtorrent
|
||||||
|
|
||||||
// allocate the piece_map to cover all pieces
|
// allocate the piece_map to cover all pieces
|
||||||
// and make them invalid (as if though we already had every piece)
|
// and make them invalid (as if though we already had every piece)
|
||||||
std::fill(m_piece_map.begin(), m_piece_map.end(), piece_pos(0, 0xfffff));
|
std::fill(m_piece_map.begin(), m_piece_map.end(), piece_pos(0, piece_pos::we_have_index));
|
||||||
}
|
}
|
||||||
|
|
||||||
void piece_picker::files_checked(
|
void piece_picker::files_checked(
|
||||||
|
@ -107,14 +108,18 @@ namespace libtorrent
|
||||||
int index = *i;
|
int index = *i;
|
||||||
assert(index >= 0);
|
assert(index >= 0);
|
||||||
assert(index < (int)m_piece_map.size());
|
assert(index < (int)m_piece_map.size());
|
||||||
assert(m_piece_map[index].index == 0xfffff);
|
assert(m_piece_map[index].index == piece_pos::we_have_index);
|
||||||
|
|
||||||
int peer_count = m_piece_map[index].peer_count;
|
int peer_count = m_piece_map[index].peer_count;
|
||||||
assert(peer_count == 0);
|
assert(peer_count == 0);
|
||||||
assert(m_piece_info.size() == 2);
|
assert(m_piece_info.size() == 2);
|
||||||
|
|
||||||
m_piece_map[index].index = (int)m_piece_info[peer_count].size();
|
piece_pos& p = m_piece_map[index];
|
||||||
m_piece_info[peer_count].push_back(index);
|
std::vector<std::vector<int> >& dst_vec = pick_piece_info_vector(p.downloading
|
||||||
|
, p.filtered);
|
||||||
|
assert((int)dst_vec.size() > peer_count);
|
||||||
|
p.index = (int)dst_vec[peer_count].size();
|
||||||
|
dst_vec[peer_count].push_back(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have fast resume info
|
// if we have fast resume info
|
||||||
|
@ -184,14 +189,14 @@ namespace libtorrent
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i->index == 0xfffff)
|
if (i->index == piece_pos::we_have_index)
|
||||||
{
|
{
|
||||||
assert(t == 0 || t->have_piece(index));
|
assert(t == 0 || t->have_piece(index));
|
||||||
assert(i->downloading == 0);
|
assert(i->downloading == 0);
|
||||||
|
|
||||||
// make sure there's no entry
|
// make sure there's no entry
|
||||||
// with this index. (there shouldn't
|
// with this index. (there shouldn't
|
||||||
// be since the piece_map is 0xfffff)
|
// be since the piece_map is piece_pos::we_have_index)
|
||||||
for (std::vector<std::vector<int> >::const_iterator i = m_piece_info.begin();
|
for (std::vector<std::vector<int> >::const_iterator i = m_piece_info.begin();
|
||||||
i != m_piece_info.end(); ++i)
|
i != m_piece_info.end(); ++i)
|
||||||
{
|
{
|
||||||
|
@ -203,8 +208,7 @@ namespace libtorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
for (std::vector<std::vector<int> >::const_iterator i = m_downloading_piece_info.begin();
|
for (std::vector<std::vector<int> >::const_iterator i = m_downloading_piece_info.begin();
|
||||||
i != m_downloading_piece_info.end();
|
i != m_downloading_piece_info.end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
for (std::vector<int>::const_iterator j = i->begin();
|
for (std::vector<int>::const_iterator j = i->begin();
|
||||||
j != i->end(); ++j)
|
j != i->end(); ++j)
|
||||||
|
@ -219,7 +223,7 @@ namespace libtorrent
|
||||||
if (t != 0)
|
if (t != 0)
|
||||||
assert(!t->have_piece(index));
|
assert(!t->have_piece(index));
|
||||||
|
|
||||||
const std::vector<std::vector<int> >& c_vec = (i->downloading)?m_downloading_piece_info:m_piece_info;
|
const std::vector<std::vector<int> >& c_vec = pick_piece_info_vector(i->downloading, i->filtered);
|
||||||
assert(i->peer_count < c_vec.size());
|
assert(i->peer_count < c_vec.size());
|
||||||
const std::vector<int>& vec = c_vec[i->peer_count];
|
const std::vector<int>& vec = c_vec[i->peer_count];
|
||||||
assert(i->index < vec.size());
|
assert(i->index < vec.size());
|
||||||
|
@ -261,11 +265,30 @@ namespace libtorrent
|
||||||
return 1.f;
|
return 1.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
void piece_picker::move(bool downloading, int peer_count, int elem_index)
|
std::vector<std::vector<int> >& piece_picker::pick_piece_info_vector(
|
||||||
|
bool downloading, bool filtered)
|
||||||
|
{
|
||||||
|
return filtered
|
||||||
|
?m_filtered_piece_info
|
||||||
|
:(downloading?m_downloading_piece_info:m_piece_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::vector<int> > const& piece_picker::pick_piece_info_vector(
|
||||||
|
bool downloading, bool filtered) const
|
||||||
|
{
|
||||||
|
return filtered
|
||||||
|
?m_filtered_piece_info
|
||||||
|
:(downloading?m_downloading_piece_info:m_piece_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// will update the piece with the given properties (downloading, filtered, peer_count, elem_index)
|
||||||
|
// to place it at the correct position in the vectors.
|
||||||
|
void piece_picker::move(bool downloading, bool filtered, int peer_count, int elem_index)
|
||||||
{
|
{
|
||||||
assert(peer_count >= 0);
|
assert(peer_count >= 0);
|
||||||
assert(elem_index >= 0);
|
assert(elem_index >= 0);
|
||||||
std::vector<std::vector<int> >& src_vec = (downloading)?m_downloading_piece_info:m_piece_info;
|
std::vector<std::vector<int> >& src_vec(pick_piece_info_vector(downloading, filtered));
|
||||||
|
|
||||||
assert((int)src_vec.size() > peer_count);
|
assert((int)src_vec.size() > peer_count);
|
||||||
assert((int)src_vec[peer_count].size() > elem_index);
|
assert((int)src_vec[peer_count].size() > elem_index);
|
||||||
|
@ -276,7 +299,7 @@ namespace libtorrent
|
||||||
|
|
||||||
assert(p.downloading != downloading || (int)p.peer_count != peer_count);
|
assert(p.downloading != downloading || (int)p.peer_count != peer_count);
|
||||||
|
|
||||||
std::vector<std::vector<int> >& dst_vec = (p.downloading)?m_downloading_piece_info:m_piece_info;
|
std::vector<std::vector<int> >& dst_vec(pick_piece_info_vector(p.downloading, p.filtered));
|
||||||
|
|
||||||
if (dst_vec.size() <= p.peer_count)
|
if (dst_vec.size() <= p.peer_count)
|
||||||
{
|
{
|
||||||
|
@ -308,21 +331,20 @@ namespace libtorrent
|
||||||
}
|
}
|
||||||
|
|
||||||
src_vec[peer_count].pop_back();
|
src_vec[peer_count].pop_back();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void piece_picker::remove(bool downloading, int peer_count, int elem_index)
|
void piece_picker::remove(bool downloading, bool filtered, int peer_count, int elem_index)
|
||||||
{
|
{
|
||||||
assert(peer_count >= 0);
|
assert(peer_count >= 0);
|
||||||
assert(elem_index >= 0);
|
assert(elem_index >= 0);
|
||||||
|
|
||||||
std::vector<std::vector<int> >& src_vec = (downloading)?m_downloading_piece_info:m_piece_info;
|
std::vector<std::vector<int> >& src_vec(pick_piece_info_vector(downloading, filtered));
|
||||||
|
|
||||||
assert((int)src_vec.size() > peer_count);
|
assert((int)src_vec.size() > peer_count);
|
||||||
assert((int)src_vec[peer_count].size() > elem_index);
|
assert((int)src_vec[peer_count].size() > elem_index);
|
||||||
|
|
||||||
int index = src_vec[peer_count][elem_index];
|
int index = src_vec[peer_count][elem_index];
|
||||||
m_piece_map[index].index = 0xfffff;
|
m_piece_map[index].index = piece_pos::we_have_index;
|
||||||
|
|
||||||
if (downloading)
|
if (downloading)
|
||||||
{
|
{
|
||||||
|
@ -360,7 +382,8 @@ namespace libtorrent
|
||||||
m_downloads.erase(i);
|
m_downloads.erase(i);
|
||||||
|
|
||||||
m_piece_map[index].downloading = 0;
|
m_piece_map[index].downloading = 0;
|
||||||
move(true, m_piece_map[index].peer_count, m_piece_map[index].index);
|
piece_pos& p = m_piece_map[index];
|
||||||
|
move(true, p.filtered, p.peer_count, p.index);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// integrity_check();
|
// integrity_check();
|
||||||
|
@ -381,9 +404,10 @@ namespace libtorrent
|
||||||
|
|
||||||
// if we have the piece, we don't have to move
|
// if we have the piece, we don't have to move
|
||||||
// any entries in the piece_info vector
|
// any entries in the piece_info vector
|
||||||
if (index == 0xfffff) return;
|
if (index == piece_pos::we_have_index) return;
|
||||||
|
|
||||||
move(m_piece_map[i].downloading, peer_count, index);
|
piece_pos& p = m_piece_map[i];
|
||||||
|
move(p.downloading, p.filtered, peer_count, index);
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// integrity_check();
|
// integrity_check();
|
||||||
|
@ -407,10 +431,15 @@ namespace libtorrent
|
||||||
if (m_piece_map[i].peer_count > 0)
|
if (m_piece_map[i].peer_count > 0)
|
||||||
m_piece_map[i].peer_count--;
|
m_piece_map[i].peer_count--;
|
||||||
|
|
||||||
if (index == 0xfffff) return;
|
if (index == piece_pos::we_have_index) return;
|
||||||
move(m_piece_map[i].downloading, peer_count, index);
|
piece_pos& p = m_piece_map[i];
|
||||||
|
move(p.downloading, p.filtered, peer_count, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this is used to indicate that we succesfully have
|
||||||
|
// downloaded a piece, and that no further attempts
|
||||||
|
// to pick that piece should be made. The piece will
|
||||||
|
// be removed from the available piece list.
|
||||||
void piece_picker::we_have(int index)
|
void piece_picker::we_have(int index)
|
||||||
{
|
{
|
||||||
assert(index >= 0);
|
assert(index >= 0);
|
||||||
|
@ -421,16 +450,74 @@ namespace libtorrent
|
||||||
|
|
||||||
assert(m_piece_map[index].downloading == 1);
|
assert(m_piece_map[index].downloading == 1);
|
||||||
|
|
||||||
assert(info_index != 0xfffff);
|
assert(info_index != piece_pos::we_have_index);
|
||||||
remove(m_piece_map[index].downloading, peer_count, info_index);
|
piece_pos& p = m_piece_map[index];
|
||||||
|
remove(p.downloading, p.filtered, peer_count, info_index);
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// integrity_check();
|
// integrity_check();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void piece_picker::pick_pieces(const std::vector<bool>& pieces,
|
|
||||||
std::vector<piece_block>& interesting_pieces,
|
void piece_picker::mark_as_filtered(int index)
|
||||||
int num_blocks) const
|
{
|
||||||
|
assert(index >= 0);
|
||||||
|
assert(index < (int)m_piece_map.size());
|
||||||
|
|
||||||
|
piece_pos& p = m_piece_map[index];
|
||||||
|
if (p.filtered == 1) return;
|
||||||
|
p.filtered = 1;
|
||||||
|
if (p.index != piece_pos::we_have_index)
|
||||||
|
move(p.downloading, false, p.peer_count, p.index);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
integrity_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function can be used for pieces that we don't
|
||||||
|
// have, but have marked as filtered (so we didn't
|
||||||
|
// want to download them) but later want to enable for
|
||||||
|
// downloading, then we call this function and it will
|
||||||
|
// be inserted in the available piece list again
|
||||||
|
void piece_picker::mark_as_unfiltered(int index)
|
||||||
|
{
|
||||||
|
assert(index >= 0);
|
||||||
|
assert(index < (int)m_piece_map.size());
|
||||||
|
|
||||||
|
piece_pos& p = m_piece_map[index];
|
||||||
|
if (p.filtered == 0) return;
|
||||||
|
p.filtered = 0;
|
||||||
|
if (p.index != piece_pos::we_have_index)
|
||||||
|
move(p.downloading, true, p.peer_count, p.index);
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
integrity_check();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool piece_picker::is_filtered(int index) const
|
||||||
|
{
|
||||||
|
assert(index >= 0);
|
||||||
|
assert(index < (int)m_piece_map.size());
|
||||||
|
|
||||||
|
return m_piece_map[index].filtered == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void piece_picker::filtered_pieces(std::vector<bool>& mask) const
|
||||||
|
{
|
||||||
|
mask.resize(m_piece_map.size());
|
||||||
|
std::vector<bool>::iterator j = mask.begin();
|
||||||
|
for (std::vector<piece_pos>::const_iterator i = m_piece_map.begin(),
|
||||||
|
end(m_piece_map.end()); i != end; ++i, ++j)
|
||||||
|
{
|
||||||
|
*j = i->filtered == 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void piece_picker::pick_pieces(const std::vector<bool>& pieces
|
||||||
|
, std::vector<piece_block>& interesting_pieces
|
||||||
|
, int num_blocks) const
|
||||||
{
|
{
|
||||||
assert(num_blocks > 0);
|
assert(num_blocks > 0);
|
||||||
assert(pieces.size() == m_piece_map.size());
|
assert(pieces.size() == m_piece_map.size());
|
||||||
|
@ -445,6 +532,8 @@ namespace libtorrent
|
||||||
// parts of them may be free for download as well, the
|
// parts of them may be free for download as well, the
|
||||||
// partially donloaded pieces will be prioritized
|
// partially donloaded pieces will be prioritized
|
||||||
assert(m_piece_info.begin() != m_piece_info.end());
|
assert(m_piece_info.begin() != m_piece_info.end());
|
||||||
|
// +1 is to ignore pieces that no peer has. The bucket with index 0 contains
|
||||||
|
// pieces that 0 other peers has.
|
||||||
std::vector<std::vector<int> >::const_iterator free = m_piece_info.begin()+1;
|
std::vector<std::vector<int> >::const_iterator free = m_piece_info.begin()+1;
|
||||||
assert(m_downloading_piece_info.begin() != m_downloading_piece_info.end());
|
assert(m_downloading_piece_info.begin() != m_downloading_piece_info.end());
|
||||||
std::vector<std::vector<int> >::const_iterator partial = m_downloading_piece_info.begin()+1;
|
std::vector<std::vector<int> >::const_iterator partial = m_downloading_piece_info.begin()+1;
|
||||||
|
@ -472,10 +561,10 @@ namespace libtorrent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int piece_picker::add_interesting_blocks(const std::vector<int>& piece_list,
|
int piece_picker::add_interesting_blocks(const std::vector<int>& piece_list
|
||||||
const std::vector<bool>& pieces,
|
, const std::vector<bool>& pieces
|
||||||
std::vector<piece_block>& interesting_blocks,
|
, std::vector<piece_block>& interesting_blocks
|
||||||
int num_blocks) const
|
, int num_blocks) const
|
||||||
{
|
{
|
||||||
assert(num_blocks > 0);
|
assert(num_blocks > 0);
|
||||||
|
|
||||||
|
@ -571,7 +660,7 @@ namespace libtorrent
|
||||||
assert(block.piece_index < (int)m_piece_map.size());
|
assert(block.piece_index < (int)m_piece_map.size());
|
||||||
assert(block.block_index < (int)max_blocks_per_piece);
|
assert(block.block_index < (int)max_blocks_per_piece);
|
||||||
|
|
||||||
if (m_piece_map[block.piece_index].index == 0xfffff) return true;
|
if (m_piece_map[block.piece_index].index == piece_pos::we_have_index) return true;
|
||||||
if (m_piece_map[block.piece_index].downloading == 0) return false;
|
if (m_piece_map[block.piece_index].downloading == 0) return false;
|
||||||
std::vector<downloading_piece>::const_iterator i
|
std::vector<downloading_piece>::const_iterator i
|
||||||
= std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index));
|
= std::find_if(m_downloads.begin(), m_downloads.end(), has_index(block.piece_index));
|
||||||
|
@ -594,7 +683,7 @@ namespace libtorrent
|
||||||
if (p.downloading == 0)
|
if (p.downloading == 0)
|
||||||
{
|
{
|
||||||
p.downloading = 1;
|
p.downloading = 1;
|
||||||
move(false, p.peer_count, p.index);
|
move(false, p.filtered, p.peer_count, p.index);
|
||||||
|
|
||||||
downloading_piece dp;
|
downloading_piece dp;
|
||||||
dp.index = block.piece_index;
|
dp.index = block.piece_index;
|
||||||
|
@ -627,10 +716,12 @@ namespace libtorrent
|
||||||
assert(block.block_index < blocks_in_piece(block.piece_index));
|
assert(block.block_index < blocks_in_piece(block.piece_index));
|
||||||
|
|
||||||
piece_pos& p = m_piece_map[block.piece_index];
|
piece_pos& p = m_piece_map[block.piece_index];
|
||||||
|
if (p.index == piece_pos::we_have_index) return;
|
||||||
|
|
||||||
if (p.downloading == 0)
|
if (p.downloading == 0)
|
||||||
{
|
{
|
||||||
p.downloading = 1;
|
p.downloading = 1;
|
||||||
move(false, p.peer_count, p.index);
|
move(false, p.filtered, p.peer_count, p.index);
|
||||||
|
|
||||||
downloading_piece dp;
|
downloading_piece dp;
|
||||||
dp.index = block.piece_index;
|
dp.index = block.piece_index;
|
||||||
|
@ -750,7 +841,8 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
m_downloads.erase(i);
|
m_downloads.erase(i);
|
||||||
m_piece_map[block.piece_index].downloading = 0;
|
m_piece_map[block.piece_index].downloading = 0;
|
||||||
move(true, m_piece_map[block.piece_index].peer_count, m_piece_map[block.piece_index].index);
|
piece_pos& p = m_piece_map[block.piece_index];
|
||||||
|
move(true, p.filtered, p.peer_count, p.index);
|
||||||
}
|
}
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
// integrity_check();
|
// integrity_check();
|
||||||
|
|
|
@ -124,8 +124,7 @@ namespace
|
||||||
busy_pieces.reserve(10);
|
busy_pieces.reserve(10);
|
||||||
|
|
||||||
for (std::vector<piece_block>::iterator i = interesting_pieces.begin();
|
for (std::vector<piece_block>::iterator i = interesting_pieces.begin();
|
||||||
i != interesting_pieces.end();
|
i != interesting_pieces.end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
if (p.is_downloading(*i))
|
if (p.is_downloading(*i))
|
||||||
{
|
{
|
||||||
|
|
|
@ -136,11 +136,13 @@ namespace libtorrent { namespace detail
|
||||||
// lock the session to add the new torrent
|
// lock the session to add the new torrent
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_mutex);
|
boost::mutex::scoped_lock l(m_mutex);
|
||||||
if (!t->abort)
|
if (t->abort)
|
||||||
{
|
{
|
||||||
boost::mutex::scoped_lock l(m_ses.m_mutex);
|
|
||||||
m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr));
|
|
||||||
m_torrents.pop_front();
|
m_torrents.pop_front();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
boost::mutex::scoped_lock l2(m_ses.m_mutex);
|
||||||
|
m_ses.m_torrents.insert(std::make_pair(t->info_hash, t->torrent_ptr));
|
||||||
if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info))
|
if (t->torrent_ptr->is_seed() && m_ses.m_alerts.should_post(alert::info))
|
||||||
{
|
{
|
||||||
m_ses.m_alerts.post_alert(torrent_finished_alert(
|
m_ses.m_alerts.post_alert(torrent_finished_alert(
|
||||||
|
@ -155,7 +157,7 @@ namespace libtorrent { namespace detail
|
||||||
{
|
{
|
||||||
t->torrent_ptr->get_policy().peer_from_tracker(*i, id);
|
t->torrent_ptr->get_policy().peer_from_tracker(*i, id);
|
||||||
}
|
}
|
||||||
}
|
m_torrents.pop_front();
|
||||||
}
|
}
|
||||||
catch(const std::exception& e)
|
catch(const std::exception& e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -1119,13 +1119,11 @@ namespace libtorrent
|
||||||
|
|
||||||
std::vector<char> piece_data(static_cast<int>(m_info.piece_length()));
|
std::vector<char> piece_data(static_cast<int>(m_info.piece_length()));
|
||||||
|
|
||||||
|
// this maps a piece hash to piece index. It will be
|
||||||
|
// build the first time it is used (to save time if it
|
||||||
|
// isn't needed)
|
||||||
std::multimap<sha1_hash, int> hash_to_piece;
|
std::multimap<sha1_hash, int> hash_to_piece;
|
||||||
// build the hash-map, that maps hashes to pieces
|
// build the hash-map, that maps hashes to pieces
|
||||||
for (int i = 0; i < m_info.num_pieces(); ++i)
|
|
||||||
{
|
|
||||||
hash_to_piece.insert(std::make_pair(m_info.hash_for_piece(i), i));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int current_slot = 0; current_slot < m_info.num_pieces(); ++current_slot)
|
for (int current_slot = 0; current_slot < m_info.num_pieces(); ++current_slot)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -1137,6 +1135,14 @@ namespace libtorrent
|
||||||
, 0
|
, 0
|
||||||
, static_cast<int>(m_info.piece_size(current_slot)));
|
, static_cast<int>(m_info.piece_size(current_slot)));
|
||||||
|
|
||||||
|
if (hash_to_piece.empty())
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_info.num_pieces(); ++i)
|
||||||
|
{
|
||||||
|
hash_to_piece.insert(std::make_pair(m_info.hash_for_piece(i), i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int piece_index = identify_data(
|
int piece_index = identify_data(
|
||||||
piece_data
|
piece_data
|
||||||
, current_slot
|
, current_slot
|
||||||
|
|
|
@ -555,6 +555,34 @@ namespace libtorrent
|
||||||
return m_username + ":" + m_password;
|
return m_username + ":" + m_password;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void torrent::filter_piece(int index, bool filter)
|
||||||
|
{
|
||||||
|
// this call is only valid on torrents with metadata
|
||||||
|
assert(m_picker.get());
|
||||||
|
assert(index >= 0);
|
||||||
|
assert(index < m_torrent_file.num_pieces());
|
||||||
|
|
||||||
|
if (filter) m_picker->mark_as_filtered(index);
|
||||||
|
else m_picker->mark_as_unfiltered(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool torrent::is_piece_filtered(int index) const
|
||||||
|
{
|
||||||
|
// this call is only valid on torrents with metadata
|
||||||
|
assert(m_picker.get());
|
||||||
|
assert(index >= 0);
|
||||||
|
assert(index < m_torrent_file.num_pieces());
|
||||||
|
|
||||||
|
return m_picker->is_filtered(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
void torrent::filtered_pieces(std::vector<bool>& bitmask) const
|
||||||
|
{
|
||||||
|
// this call is only valid on torrents with metadata
|
||||||
|
assert(m_picker.get());
|
||||||
|
m_picker->filtered_pieces(bitmask);
|
||||||
|
}
|
||||||
|
|
||||||
void torrent::replace_trackers(std::vector<announce_entry> const& urls)
|
void torrent::replace_trackers(std::vector<announce_entry> const& urls)
|
||||||
{
|
{
|
||||||
assert(!urls.empty());
|
assert(!urls.empty());
|
||||||
|
|
|
@ -76,63 +76,10 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
#if defined(_MSC_VER) && _MSC_VER < 1300
|
void throw_invalid_handle()
|
||||||
|
|
||||||
template<class T>
|
|
||||||
struct transform_void{ typedef T type; };
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct transform_void<void> { typedef int type; };
|
|
||||||
|
|
||||||
template<class Ret>
|
|
||||||
struct void_call_wrapper
|
|
||||||
{
|
{
|
||||||
template<class F>
|
throw_invalid_handle();
|
||||||
static Ret call(F f, torrent& t)
|
|
||||||
{
|
|
||||||
return f(t);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
template<>
|
|
||||||
struct void_call_wrapper<void>
|
|
||||||
{
|
|
||||||
template<class F>
|
|
||||||
static int call(F f, torrent& t)
|
|
||||||
{
|
|
||||||
f(t);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<class Ret, class F>
|
|
||||||
transform_void<Ret>::type call_member(
|
|
||||||
detail::session_impl* ses
|
|
||||||
, detail::checker_impl* chk
|
|
||||||
, sha1_hash const& hash
|
|
||||||
, F f)
|
|
||||||
{
|
|
||||||
typedef typename transform_void<Ret>::type ret;
|
|
||||||
if (ses == 0) throw invalid_handle();
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock l(ses->m_mutex);
|
|
||||||
torrent* t = ses->find_torrent(hash);
|
|
||||||
if (t != 0) return void_call_wrapper<Ret>::call(f, *t);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (chk)
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock l(chk->m_mutex);
|
|
||||||
|
|
||||||
detail::piece_checker_data* d = chk->find_torrent(hash);
|
|
||||||
if (d != 0) return void_call_wrapper<Ret>::call(f, *d->torrent_ptr);
|
|
||||||
}
|
|
||||||
throw invalid_handle();
|
|
||||||
}
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
template<class Ret, class F>
|
template<class Ret, class F>
|
||||||
Ret call_member(
|
Ret call_member(
|
||||||
|
@ -141,14 +88,7 @@ namespace libtorrent
|
||||||
, sha1_hash const& hash
|
, sha1_hash const& hash
|
||||||
, F f)
|
, F f)
|
||||||
{
|
{
|
||||||
if (ses == 0) throw invalid_handle();
|
if (ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock l(ses->m_mutex);
|
|
||||||
torrent* t = ses->find_torrent(hash);
|
|
||||||
if (t != 0) return f(*t);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (chk)
|
if (chk)
|
||||||
{
|
{
|
||||||
|
@ -157,10 +97,15 @@ namespace libtorrent
|
||||||
detail::piece_checker_data* d = chk->find_torrent(hash);
|
detail::piece_checker_data* d = chk->find_torrent(hash);
|
||||||
if (d != 0) return f(*d->torrent_ptr);
|
if (d != 0) return f(*d->torrent_ptr);
|
||||||
}
|
}
|
||||||
throw invalid_handle();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
{
|
||||||
|
boost::mutex::scoped_lock l(ses->m_mutex);
|
||||||
|
torrent* t = ses->find_torrent(hash);
|
||||||
|
if (t != 0) return f(*t);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_invalid_handle();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
@ -281,13 +226,7 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
|
||||||
torrent* t = m_ses->find_torrent(m_info_hash);
|
|
||||||
if (t != 0) return t->status();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_chk)
|
if (m_chk)
|
||||||
{
|
{
|
||||||
|
@ -308,7 +247,36 @@ namespace libtorrent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw invalid_handle();
|
{
|
||||||
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
|
torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
|
if (t != 0) return t->status();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_invalid_handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void torrent_handle::filter_piece(int index, bool filter)
|
||||||
|
{
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
call_member<void>(m_ses, m_chk, m_info_hash
|
||||||
|
, bind(&torrent::filter_piece, _1, index, filter));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool torrent_handle::is_piece_filtered(int index) const
|
||||||
|
{
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
return call_member<bool>(m_ses, m_chk, m_info_hash
|
||||||
|
, bind(&torrent::is_piece_filtered, _1, index));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<bool> torrent_handle::filtered_pieces() const
|
||||||
|
{
|
||||||
|
INVARIANT_CHECK;
|
||||||
|
std::vector<bool> ret;
|
||||||
|
call_member<void>(m_ses, m_chk, m_info_hash
|
||||||
|
, bind(&torrent::filtered_pieces, _1, boost::ref(ret)));
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<announce_entry> const& torrent_handle::trackers() const
|
std::vector<announce_entry> const& torrent_handle::trackers() const
|
||||||
|
@ -331,7 +299,7 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
if (!has_metadata()) throw invalid_handle();
|
if (!has_metadata()) throw_invalid_handle();
|
||||||
return call_member<torrent_info const&>(m_ses, m_chk, m_info_hash
|
return call_member<torrent_info const&>(m_ses, m_chk, m_info_hash
|
||||||
, bind(&torrent::torrent_file, _1));
|
, bind(&torrent::torrent_file, _1));
|
||||||
}
|
}
|
||||||
|
@ -342,12 +310,6 @@ namespace libtorrent
|
||||||
|
|
||||||
if (m_ses == 0) return false;
|
if (m_ses == 0) return false;
|
||||||
|
|
||||||
{
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
|
||||||
torrent* t = m_ses->find_torrent(m_info_hash);
|
|
||||||
if (t != 0) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_chk)
|
if (m_chk)
|
||||||
{
|
{
|
||||||
boost::mutex::scoped_lock l(m_chk->m_mutex);
|
boost::mutex::scoped_lock l(m_chk->m_mutex);
|
||||||
|
@ -355,6 +317,12 @@ namespace libtorrent
|
||||||
if (d != 0) return true;
|
if (d != 0) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
|
torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
|
if (t != 0) return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,11 +467,11 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
torrent* t = m_ses->find_torrent(m_info_hash);
|
torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
if (t == 0) throw invalid_handle();
|
if (t == 0) throw_invalid_handle();
|
||||||
|
|
||||||
peer_id id;
|
peer_id id;
|
||||||
std::fill(id.begin(), id.end(), 0);
|
std::fill(id.begin(), id.end(), 0);
|
||||||
|
@ -515,11 +483,11 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
torrent* t = m_ses->find_torrent(m_info_hash);
|
torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
if (t == 0) throw invalid_handle();
|
if (t == 0) throw_invalid_handle();
|
||||||
|
|
||||||
using boost::posix_time::second_clock;
|
using boost::posix_time::second_clock;
|
||||||
t->force_tracker_request(second_clock::universal_time()
|
t->force_tracker_request(second_clock::universal_time()
|
||||||
|
@ -530,11 +498,11 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
torrent* t = m_ses->find_torrent(m_info_hash);
|
torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
if (t == 0) throw invalid_handle();
|
if (t == 0) throw_invalid_handle();
|
||||||
|
|
||||||
t->force_tracker_request();
|
t->force_tracker_request();
|
||||||
}
|
}
|
||||||
|
@ -557,7 +525,7 @@ namespace libtorrent
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
v.clear();
|
v.clear();
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
|
|
||||||
|
@ -565,8 +533,7 @@ namespace libtorrent
|
||||||
if (t == 0) return;
|
if (t == 0) return;
|
||||||
|
|
||||||
for (torrent::const_peer_iterator i = t->begin();
|
for (torrent::const_peer_iterator i = t->begin();
|
||||||
i != t->end();
|
i != t->end(); ++i)
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
peer_connection* peer = i->second;
|
peer_connection* peer = i->second;
|
||||||
|
|
||||||
|
@ -580,6 +547,8 @@ namespace libtorrent
|
||||||
const stat& statistics = peer->statistics();
|
const stat& statistics = peer->statistics();
|
||||||
p.down_speed = statistics.download_rate();
|
p.down_speed = statistics.download_rate();
|
||||||
p.up_speed = statistics.upload_rate();
|
p.up_speed = statistics.upload_rate();
|
||||||
|
p.payload_down_speed = statistics.download_payload_rate();
|
||||||
|
p.payload_up_speed = statistics.upload_payload_rate();
|
||||||
p.id = peer->get_peer_id();
|
p.id = peer->get_peer_id();
|
||||||
p.ip = peer->get_socket()->sender();
|
p.ip = peer->get_socket()->sender();
|
||||||
|
|
||||||
|
@ -632,7 +601,7 @@ namespace libtorrent
|
||||||
|
|
||||||
bool torrent_handle::send_chat_message(address ip, std::string message) const
|
bool torrent_handle::send_chat_message(address ip, std::string message) const
|
||||||
{
|
{
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
const torrent* t = m_ses->find_torrent(m_info_hash);
|
const torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
|
@ -668,7 +637,7 @@ namespace libtorrent
|
||||||
{
|
{
|
||||||
INVARIANT_CHECK;
|
INVARIANT_CHECK;
|
||||||
|
|
||||||
if (m_ses == 0) throw invalid_handle();
|
if (m_ses == 0) throw_invalid_handle();
|
||||||
|
|
||||||
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
boost::mutex::scoped_lock l(m_ses->m_mutex);
|
||||||
torrent* t = m_ses->find_torrent(m_info_hash);
|
torrent* t = m_ses->find_torrent(m_info_hash);
|
||||||
|
|
|
@ -501,9 +501,7 @@ namespace libtorrent
|
||||||
tracker_connections_t keep_connections;
|
tracker_connections_t keep_connections;
|
||||||
|
|
||||||
for (tracker_connections_t::const_iterator i =
|
for (tracker_connections_t::const_iterator i =
|
||||||
m_connections.begin();
|
m_connections.begin(); i != m_connections.end(); ++i)
|
||||||
i != m_connections.end();
|
|
||||||
++i)
|
|
||||||
{
|
{
|
||||||
if (!(*i)->has_requester()) keep_connections.push_back(*i);
|
if (!(*i)->has_requester()) keep_connections.push_back(*i);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue