factor out file progress tracking from the torrent class

This commit is contained in:
arvidn 2015-06-28 22:47:11 -04:00
parent 2d1db68ea6
commit e2784df13c
11 changed files with 382 additions and 189 deletions

View File

@ -684,6 +684,7 @@ SOURCES =
performance_counters
resolver
session_settings
file_progress
# -- extensions --
metadata_transfer

View File

@ -166,6 +166,7 @@ nobase_include_HEADERS = \
aux_/session_settings.hpp \
aux_/session_interface.hpp \
aux_/time.hpp \
aux_/file_progress.hpp \
\
extensions/lt_trackers.hpp \
extensions/metadata_transfer.hpp \

View File

@ -0,0 +1,78 @@
/*
Copyright (c) 2015, 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.
*/
#ifndef TORRENT_FILE_PROGRESS_HPP_INCLUDE
#define TORRENT_FILE_PROGRESS_HPP_INCLUDE
#include <vector>
#include <boost/cstdint.hpp>
namespace libtorrent
{
class piece_picker;
class file_storage;
class alert_manager;
class torrent_handle;
namespace aux
{
struct file_progress
{
file_progress();
void init(piece_picker const& picker
, file_storage const& fs);
void export_progress(std::vector<boost::int64_t> &fp);
void clear();
void update(file_storage const& fs, int index
, alert_manager* alerts, torrent_handle const& h);
#if TORRENT_USE_INVARIANT_CHECKS
void check_invariant(file_storage const& fs) const;
#endif
private:
// this vector contains the number of bytes completely
// downloaded (as in passed-hash-check) in each file.
// this lets us trigger on individual files completing
// the vector is allocated lazily, when file progress
// is first queried by the client
std::vector<boost::uint64_t> m_file_progress;
};
} }
#endif

View File

@ -74,6 +74,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/vector_utils.hpp"
#include "libtorrent/linked_list.hpp"
#include "libtorrent/debug.hpp"
#include "libtorrent/aux_/file_progress.hpp"
#if TORRENT_COMPLETE_TYPES_REQUIRED
#include "libtorrent/peer_connection.hpp"
@ -99,10 +100,6 @@ namespace libtorrent
struct listen_socket_t;
TORRENT_EXTRA_EXPORT void initialize_file_progress(
std::vector<boost::uint64_t>& file_progress
, piece_picker const& picker, file_storage const& fs);
namespace aux
{
struct piece_checker_data;
@ -1269,12 +1266,8 @@ namespace libtorrent
// TODO: this wastes 5 bits per file
std::vector<boost::uint8_t> m_file_priority;
// this vector contains the number of bytes completely
// downloaded (as in passed-hash-check) in each file.
// this lets us trigger on individual files completing
// the vector is allocated lazily, when file progress
// is first queried by the client
std::vector<boost::uint64_t> m_file_progress;
// this object is used to track download progress of individual files
aux::file_progress m_file_progress;
// these are the pieces we're currently
// suggesting to peers.

View File

@ -142,6 +142,7 @@ libtorrent_rasterbar_la_SOURCES = \
utp_stream.cpp \
web_peer_connection.cpp \
xml_parse.cpp \
file_progress.cpp \
\
$(KADEMLIA_SOURCES) \
$(ASIO_OPENSSL_SOURCES)

170
src/file_progress.cpp Normal file
View File

@ -0,0 +1,170 @@
/*
Copyright (c) 2015, 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 "libtorrent/piece_picker.hpp"
#include "libtorrent/file_storage.hpp"
#include "libtorrent/alert_manager.hpp"
#include "libtorrent/aux_/file_progress.hpp"
#include "libtorrent/alert_types.hpp"
namespace libtorrent { namespace aux
{
file_progress::file_progress()
{
}
void file_progress::init(piece_picker const& picker, file_storage const& fs)
{
if (!m_file_progress.empty()) return;
int num_pieces = fs.num_pieces();
int num_files = fs.num_files();
m_file_progress.resize(num_files, 0);
std::fill(m_file_progress.begin(), m_file_progress.end(), 0);
// initialize the progress of each file
const int piece_size = fs.piece_length();
boost::uint64_t off = 0;
boost::uint64_t total_size = fs.total_size();
int file_index = 0;
for (int piece = 0; piece < num_pieces; ++piece, off += piece_size)
{
TORRENT_ASSERT(file_index < fs.num_files());
boost::int64_t file_offset = off - fs.file_offset(file_index);
TORRENT_ASSERT(file_offset >= 0);
while (file_offset >= fs.file_size(file_index))
{
++file_index;
TORRENT_ASSERT(file_index < fs.num_files());
file_offset = off - fs.file_offset(file_index);
TORRENT_ASSERT(file_offset >= 0);
}
TORRENT_ASSERT(file_offset <= fs.file_size(file_index));
if (!picker.have_piece(piece)) continue;
int size = (std::min)(boost::uint64_t(piece_size), total_size - off);
TORRENT_ASSERT(size >= 0);
while (size)
{
int add = (std::min)(boost::int64_t(size), fs.file_size(file_index) - file_offset);
TORRENT_ASSERT(add >= 0);
m_file_progress[file_index] += add;
TORRENT_ASSERT(m_file_progress[file_index]
<= fs.file_size(file_index));
size -= add;
TORRENT_ASSERT(size >= 0);
if (size > 0)
{
++file_index;
TORRENT_ASSERT(file_index < fs.num_files());
file_offset = 0;
}
}
}
}
void file_progress::export_progress(std::vector<boost::int64_t> &fp)
{
fp.resize(m_file_progress.size(), 0);
std::copy(m_file_progress.begin(), m_file_progress.end(), fp.begin());
}
void file_progress::clear()
{
std::vector<boost::uint64_t>().swap(m_file_progress);
}
// update the file progress now that we just completed downloading piece
// 'index'
void file_progress::update(file_storage const& fs, int index
, alert_manager* alerts, torrent_handle const& h)
{
if (m_file_progress.empty())
return;
const int piece_size = fs.piece_length();
boost::int64_t off = boost::int64_t(index) * piece_size;
int file_index = fs.file_index_at_offset(off);
int size = fs.piece_size(index);
for (; size > 0; ++file_index)
{
boost::int64_t file_offset = off - fs.file_offset(file_index);
TORRENT_ASSERT(file_index != fs.num_files());
TORRENT_ASSERT(file_offset <= fs.file_size(file_index));
int add = (std::min)(fs.file_size(file_index) - file_offset, (boost::int64_t)size);
m_file_progress[file_index] += add;
TORRENT_ASSERT(m_file_progress[file_index]
<= fs.file_size(file_index));
// TODO: it would be nice to not depend on alert_manager here
if (m_file_progress[file_index] >= fs.file_size(file_index) && alerts)
{
if (!fs.pad_file_at(file_index))
{
if (alerts->should_post<file_completed_alert>())
{
// this file just completed, post alert
alerts->emplace_alert<file_completed_alert>(h, file_index);
}
}
}
size -= add;
off += add;
TORRENT_ASSERT(size >= 0);
}
}
#if TORRENT_USE_INVARIANT_CHECKS
void file_progress::check_invariant(file_storage const& fs) const
{
if (!m_file_progress.empty())
{
for (std::vector<boost::uint64_t>::const_iterator i = m_file_progress.begin()
, end(m_file_progress.end()); i != end; ++i)
{
int index = i - m_file_progress.begin();
TORRENT_ASSERT(*i <= fs.file_size(index));
}
}
}
#endif
} }

View File

@ -93,6 +93,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/resolver_interface.hpp"
#include "libtorrent/alloca.hpp"
#include "libtorrent/resolve_links.hpp"
#include "libtorrent/aux_/file_progress.hpp"
#ifndef TORRENT_DISABLE_LOGGING
#include "libtorrent/aux_/session_impl.hpp" // for tracker_logger
@ -803,7 +804,7 @@ namespace libtorrent
update_gauge();
std::vector<boost::uint64_t>().swap(m_file_progress);
m_file_progress.clear();
if (m_resume_data)
{
@ -2560,7 +2561,7 @@ namespace libtorrent
// file progress is allocated lazily, the first time the client
// asks for it
std::vector<boost::uint64_t>().swap(m_file_progress);
m_file_progress.clear();
// assume that we don't have anything
m_files_checked = false;
@ -4196,41 +4197,8 @@ namespace libtorrent
m_ses.alerts().emplace_alert<piece_finished_alert>(get_handle(), index);
// update m_file_progress (if we have one)
if (!m_file_progress.empty())
{
const int piece_size = m_torrent_file->piece_length();
boost::int64_t off = boost::int64_t(index) * piece_size;
int file_index = m_torrent_file->files().file_index_at_offset(off);
int size = m_torrent_file->piece_size(index);
file_storage const& fs = m_torrent_file->files();
for (; size > 0; ++file_index)
{
boost::int64_t file_offset = off - fs.file_offset(file_index);
TORRENT_ASSERT(file_index != fs.num_files());
TORRENT_ASSERT(file_offset <= fs.file_size(file_index));
int add = (std::min)(fs.file_size(file_index) - file_offset, (boost::int64_t)size);
m_file_progress[file_index] += add;
TORRENT_ASSERT(m_file_progress[file_index]
<= m_torrent_file->files().file_size(file_index));
if (m_file_progress[file_index] >= m_torrent_file->files().file_size(file_index))
{
if (!m_torrent_file->files().pad_file_at(file_index))
{
if (m_ses.alerts().should_post<file_completed_alert>())
{
// this file just completed, post alert
m_ses.alerts().emplace_alert<file_completed_alert>(get_handle()
, file_index);
}
}
}
size -= add;
off += add;
TORRENT_ASSERT(size >= 0);
}
}
m_file_progress.update(m_torrent_file->files(), index
, &m_ses.alerts(), get_handle());
remove_time_critical_piece(index, true);
@ -8299,7 +8267,8 @@ namespace libtorrent
m_became_seed = m_ses.session_time();
// no need for this anymore
std::vector<boost::uint64_t>().swap(m_file_progress);
m_file_progress.clear();
if (!m_announcing) return;
time_point now = aux::time_now();
@ -8824,15 +8793,7 @@ namespace libtorrent
TORRENT_ASSERT(block_size() > 0);
}
if (!m_file_progress.empty())
{
for (std::vector<boost::uint64_t>::const_iterator i = m_file_progress.begin()
, end(m_file_progress.end()); i != end; ++i)
{
int index = i - m_file_progress.begin();
TORRENT_ASSERT(*i <= m_torrent_file->files().file_size(index));
}
}
m_file_progress.check_invariant(m_torrent_file->files());
}
#endif
@ -11233,61 +11194,6 @@ namespace libtorrent
}
#endif
void initialize_file_progress(std::vector<boost::uint64_t>& file_progress
, piece_picker const& picker, file_storage const& fs)
{
int num_pieces = fs.num_pieces();
int num_files = fs.num_files();
file_progress.resize(num_files, 0);
std::fill(file_progress.begin(), file_progress.end(), 0);
// initialize the progress of each file
const int piece_size = fs.piece_length();
boost::uint64_t off = 0;
boost::uint64_t total_size = fs.total_size();
int file_index = 0;
for (int piece = 0; piece < num_pieces; ++piece, off += piece_size)
{
TORRENT_ASSERT(file_index < fs.num_files());
boost::int64_t file_offset = off - fs.file_offset(file_index);
TORRENT_ASSERT(file_offset >= 0);
while (file_offset >= fs.file_size(file_index))
{
++file_index;
TORRENT_ASSERT(file_index < fs.num_files());
file_offset = off - fs.file_offset(file_index);
TORRENT_ASSERT(file_offset >= 0);
}
TORRENT_ASSERT(file_offset <= fs.file_size(file_index));
if (!picker.have_piece(piece)) continue;
int size = (std::min)(boost::uint64_t(piece_size), total_size - off);
TORRENT_ASSERT(size >= 0);
while (size)
{
int add = (std::min)(boost::int64_t(size), fs.file_size(file_index) - file_offset);
TORRENT_ASSERT(add >= 0);
file_progress[file_index] += add;
TORRENT_ASSERT(file_progress[file_index]
<= fs.file_size(file_index));
size -= add;
TORRENT_ASSERT(size >= 0);
if (size > 0)
{
++file_index;
TORRENT_ASSERT(file_index < fs.num_files());
file_offset = 0;
}
}
}
}
void torrent::file_progress(std::vector<boost::int64_t>& fp, int flags)
{
TORRENT_ASSERT(is_single_thread());
@ -11320,21 +11226,14 @@ namespace libtorrent
}
int num_files = m_torrent_file->num_files();
if (m_file_progress.empty())
{
// This is the first time the client asks for file progress.
// allocate it and make sure it's up to date
// if this is the first time the client asks for file progress.
// allocate it and make sure it's up to date
// we cover the case where we're a seed above
TORRENT_ASSERT(has_picker());
// we cover the case where we're a seed above
TORRENT_ASSERT(has_picker());
m_file_progress.init(picker(), m_torrent_file->files());
initialize_file_progress(m_file_progress
, picker(), m_torrent_file->files());
}
fp.resize(num_files, 0);
std::copy(m_file_progress.begin(), m_file_progress.end(), fp.begin());
m_file_progress.export_progress(fp);
if (flags & torrent_handle::piece_granularity)
return;

View File

@ -141,7 +141,8 @@ test-suite libtorrent :
test_peer_classes.cpp
test_settings_pack.cpp
test_fence.cpp
test_dos_blocker.cpp ]
test_dos_blocker.cpp
test_file_progress.cpp ]
[ run test_storage.cpp ]
[ run test_session.cpp ]

View File

@ -32,7 +32,8 @@ test_programs = \
test_session \
test_web_seed \
test_url_seed \
test_remap_files
test_remap_files \
test_file_progress
if ENABLE_TESTS
check_PROGRAMS = $(test_programs)

110
test/test_file_progress.cpp Normal file
View File

@ -0,0 +1,110 @@
/*
Copyright (c) 2015, 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 "test_utils.hpp"
#include "libtorrent/aux_/file_progress.hpp"
#include "libtorrent/file_storage.hpp"
#include "libtorrent/piece_picker.hpp"
using namespace libtorrent;
TORRENT_TEST(init)
{
// test the init function to make sure it assigns
// the correct number of bytes across the files
const int piece_size = 256;
file_storage fs;
fs.add_file("torrent/1", 0);
fs.add_file("torrent/2", 10);
fs.add_file("torrent/3", 20);
fs.add_file("torrent/4", 30);
fs.add_file("torrent/5", 40);
fs.add_file("torrent/6", 100000);
fs.add_file("torrent/7", 30);
fs.set_piece_length(piece_size);
fs.set_num_pieces((fs.total_size() + piece_size - 1) / piece_size);
for (int idx = 0; idx < fs.num_pieces(); ++idx)
{
piece_picker picker;
picker.init(4, fs.total_size() % 4, fs.num_pieces());
picker.we_have(idx);
aux::file_progress fp;
fp.init(picker, fs);
std::vector<boost::int64_t> vec;
fp.export_progress(vec);
boost::uint64_t sum = 0;
for (int i = 0; i < int(vec.size()); ++i)
sum += vec[i];
TEST_EQUAL(sum, fs.piece_size(idx));
}
}
TORRENT_TEST(init2)
{
// test the init function to make sure it assigns
// the correct number of bytes across the files
const int piece_size = 256;
file_storage fs;
fs.add_file("torrent/1", 100000);
fs.add_file("torrent/2", 10);
fs.set_piece_length(piece_size);
fs.set_num_pieces((fs.total_size() + piece_size - 1) / piece_size);
for (int idx = 0; idx < fs.num_pieces(); ++idx)
{
piece_picker picker;
picker.init(4, fs.total_size() % 4, fs.num_pieces());
picker.we_have(idx);
std::vector<boost::int64_t> vec;
aux::file_progress fp;
fp.init(picker, fs);
fp.export_progress(vec);
boost::uint64_t sum = 0;
for (int i = 0; i < vec.size(); ++i)
sum += vec[i];
TEST_EQUAL(int(sum), fs.piece_size(idx));
}
}
// TODO: test the update function too

View File

@ -204,68 +204,6 @@ TORRENT_TEST(torrent)
test_running_torrent(info, 0);
}
{
// test the initialize_file_progress function to make sure it assigns
// the correct number of bytes across the files
const int piece_size = 256;
file_storage fs;
fs.add_file("torrent/1", 0);
fs.add_file("torrent/2", 10);
fs.add_file("torrent/3", 20);
fs.add_file("torrent/4", 30);
fs.add_file("torrent/5", 40);
fs.add_file("torrent/6", 100000);
fs.add_file("torrent/7", 30);
fs.set_piece_length(piece_size);
fs.set_num_pieces((fs.total_size() + piece_size - 1) / piece_size);
for (int idx = 0; idx < fs.num_pieces(); ++idx)
{
piece_picker picker;
picker.init(4, fs.total_size() % 4, fs.num_pieces());
picker.we_have(idx);
std::vector<boost::uint64_t> fp;
initialize_file_progress(fp, picker, fs);
boost::uint64_t sum = 0;
for (int i = 0; i < int(fp.size()); ++i)
sum += fp[i];
TEST_EQUAL(sum, fs.piece_size(idx));
}
}
{
// test the initialize_file_progress function to make sure it assigns
// the correct number of bytes across the files
const int piece_size = 256;
file_storage fs;
fs.add_file("torrent/1", 100000);
fs.add_file("torrent/2", 10);
fs.set_piece_length(piece_size);
fs.set_num_pieces((fs.total_size() + piece_size - 1) / piece_size);
for (int idx = 0; idx < fs.num_pieces(); ++idx)
{
piece_picker picker;
picker.init(4, fs.total_size() % 4, fs.num_pieces());
picker.we_have(idx);
std::vector<boost::uint64_t> fp;
initialize_file_progress(fp, picker, fs);
boost::uint64_t sum = 0;
for (int i = 0; i < fp.size(); ++i)
sum += fp[i];
TEST_EQUAL(int(sum), fs.piece_size(idx));
}
}
}