diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp index 44540769d..9ac80be16 100644 --- a/bindings/python/src/session.cpp +++ b/bindings/python/src/session.cpp @@ -753,6 +753,8 @@ void bind_session() .def_readwrite("added_time", &add_torrent_params::added_time) .def_readwrite("completed_time", &add_torrent_params::completed_time) .def_readwrite("last_seen_complete", &add_torrent_params::last_seen_complete) + .def_readwrite("last_download", &add_torrent_params::last_download) + .def_readwrite("last_upload", &add_torrent_params::last_upload) .def_readwrite("num_complete", &add_torrent_params::num_complete) .def_readwrite("num_incomplete", &add_torrent_params::num_incomplete) .def_readwrite("num_downloaded", &add_torrent_params::num_downloaded) diff --git a/bindings/python/test.py b/bindings/python/test.py index 64b91b382..2be584f15 100644 --- a/bindings/python/test.py +++ b/bindings/python/test.py @@ -167,9 +167,11 @@ class test_torrent_handle(unittest.TestCase): sessionStart = datetime.datetime.now().replace(microsecond=0) self.setup() st = self.h.status() + for attr in dir(st): + print('%s: %s' % (attr, getattr(st, attr))) # last upload and download times are at session start time - self.assertLessEqual(abs(st.last_upload - sessionStart), datetime.timedelta(seconds=1)) - self.assertLessEqual(abs(st.last_download - sessionStart), datetime.timedelta(seconds=1)) + self.assertEqual(st.last_upload, None) + self.assertEqual(st.last_download, None) def test_serialize_trackers(self): """Test to ensure the dict contains only python built-in types""" diff --git a/docs/manual.rst b/docs/manual.rst index e1234d3b8..f168d9aaf 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -434,6 +434,12 @@ The file format is a bencoded dictionary containing the following fields: | ``seeding_time`` | integer. The number of seconds this torrent has been active | | | and seeding. | +--------------------------+--------------------------------------------------------------+ +| ``last_upload`` | integer. The number of seconds since epoch when we last | +| | uploaded payload to a peer on this torrent. | ++--------------------------+--------------------------------------------------------------+ +| ``last_download`` | integer. The number of seconds since epoch when we last | +| | downloaded payload from a peer on this torrent. | ++--------------------------+--------------------------------------------------------------+ | ``upload_rate_limit`` | integer. In case this torrent has a per-torrent upload rate | | | limit, this is that limit. In bytes per second. | +--------------------------+--------------------------------------------------------------+ diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp index 6aa2ab25a..ac1737e47 100644 --- a/include/libtorrent/add_torrent_params.hpp +++ b/include/libtorrent/add_torrent_params.hpp @@ -331,6 +331,9 @@ namespace libtorrent { // applied before the torrent is added. aux::noexcept_movable> renamed_files; + std::time_t last_download = 0; + std::time_t last_upload = 0; + #ifndef TORRENT_NO_DEPRECATE // deprecated in 1.2 diff --git a/include/libtorrent/bencode.hpp b/include/libtorrent/bencode.hpp index 5627c6512..3cfede6b1 100644 --- a/include/libtorrent/bencode.hpp +++ b/include/libtorrent/bencode.hpp @@ -195,7 +195,7 @@ namespace detail { break; case entry::preformatted_t: std::copy(e.preformatted().begin(), e.preformatted().end(), out); - ret += int(e.preformatted().size()); + ret += static_cast(e.preformatted().size()); break; case entry::undefined_t: diff --git a/include/libtorrent/block_cache.hpp b/include/libtorrent/block_cache.hpp index 6a6b78fae..ff381ef4f 100644 --- a/include/libtorrent/block_cache.hpp +++ b/include/libtorrent/block_cache.hpp @@ -442,7 +442,8 @@ namespace aux { // used to convert dirty blocks into non-dirty ones // i.e. from being part of the write cache to being part // of the read cache. it's used when flushing blocks to disk - void blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed); + // returns true if the piece entry was freed + bool blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed); // adds a block to the cache, marks it as dirty and // associates the job with it. When the block is diff --git a/include/libtorrent/disk_io_thread.hpp b/include/libtorrent/disk_io_thread.hpp index 56e230524..4ef0b457e 100644 --- a/include/libtorrent/disk_io_thread.hpp +++ b/include/libtorrent/disk_io_thread.hpp @@ -450,7 +450,8 @@ namespace aux { , span iov, span flushing, int block_base_index = 0); void flush_iovec(cached_piece_entry* pe, span iov, span flushing , int num_blocks, storage_error& error); - void iovec_flushed(cached_piece_entry* pe + // returns true if the piece entry was freed + bool iovec_flushed(cached_piece_entry* pe , int* flushing, int num_blocks, int block_offset , storage_error const& error , jobqueue_t& completed_jobs); diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 28a137471..608504f28 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -1620,9 +1620,8 @@ namespace libtorrent { // ---- // the timestamp of the last piece passed for this torrent specified in - // session_time. This is signed because it must be able to represent time - // before the session started - time_point32 m_last_download = aux::time_now32(); + // seconds since epoch. + time_point32 m_last_download{seconds32(0)}; // the number of peer connections to seeds. This should be the same as // counting the peer connections that say true for is_seed() @@ -1633,9 +1632,8 @@ namespace libtorrent { std::uint16_t m_num_connecting_seeds = 0; // the timestamp of the last byte uploaded from this torrent specified in - // session_time. This is signed because it must be able to represent time - // before the session started. - time_point32 m_last_upload = aux::time_now32(); + // seconds since epoch. + time_point32 m_last_upload{seconds32(0)}; // ---- @@ -1667,7 +1665,7 @@ namespace libtorrent { // the timestamp of the last scrape request to one of the trackers in // this torrent specified in session_time. This is signed because it must // be able to represent time before the session started - std::int16_t m_last_scrape = (std::numeric_limits::min)(); + time_point32 m_last_scrape{seconds32(0)}; #endif // ---- diff --git a/include/libtorrent/torrent_status.hpp b/include/libtorrent/torrent_status.hpp index ade2c38ff..e84a20131 100644 --- a/include/libtorrent/torrent_status.hpp +++ b/include/libtorrent/torrent_status.hpp @@ -588,9 +588,7 @@ namespace libtorrent { // the info-hash for this torrent sha1_hash info_hash; - // This value is not persistent and get set to session start time. time_point last_upload; - // This value is not persistent and get set to session start time. time_point last_download; seconds active_duration; diff --git a/simulation/test_torrent_status.cpp b/simulation/test_torrent_status.cpp index d0c679728..8c0b6be88 100644 --- a/simulation/test_torrent_status.cpp +++ b/simulation/test_torrent_status.cpp @@ -97,9 +97,9 @@ TORRENT_TEST(status_timers) TEST_EQUAL(st.finished_duration.count(), since_finish.count()); // does not upload without peers - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_upload == time_point(seconds(0))); // does not download in seeding mode - TEST_CHECK(st.last_download == start_time); + TEST_CHECK(st.last_download == time_point(seconds(0))); } return false; }); @@ -110,7 +110,6 @@ TORRENT_TEST(status_timers_last_upload) { bool ran_to_completion = false; - lt::time_point32 start_time; lt::torrent_handle handle; setup_swarm(2, swarm_test::upload @@ -123,13 +122,12 @@ TORRENT_TEST(status_timers_last_upload) if (auto ta = alert_cast(a)) { TEST_CHECK(!handle.is_valid()); - start_time = time_now(); handle = ta->handle; torrent_status st = handle.status(); // test last upload and download state before wo go throgh // torrent states - TEST_CHECK(st.last_download == start_time); - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_upload == time_point(seconds(0))); + TEST_CHECK(st.last_download == time_point(seconds(0))); } } // terminate @@ -145,7 +143,7 @@ TORRENT_TEST(status_timers_last_upload) // uploadtime is 0 seconds behind now TEST_CHECK(eq(st.last_upload, time_now())); // does not download in seeding mode - TEST_CHECK(eq(st.last_download, start_time)); + TEST_CHECK(st.last_download == time_point(seconds(0))); return false; }); TEST_CHECK(ran_to_completion); @@ -155,7 +153,6 @@ TORRENT_TEST(status_timers_time_shift_with_active_torrent) { bool ran_to_completion = false; - lt::time_point32 start_time; lt::torrent_handle handle; seconds expected_active_duration = seconds(1); bool tick_is_in_active_range = false; @@ -171,13 +168,12 @@ TORRENT_TEST(status_timers_time_shift_with_active_torrent) if (auto ta = alert_cast(a)) { TEST_CHECK(!handle.is_valid()); - start_time = time_now(); handle = ta->handle; torrent_status st = handle.status(); // test last upload and download state before wo go throgh // torrent states - TEST_CHECK(st.last_download == start_time); - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_download == time_point(seconds(0))); + TEST_CHECK(st.last_upload == time_point(seconds(0))); } } // terminate @@ -224,9 +220,9 @@ TORRENT_TEST(status_timers_time_shift_with_active_torrent) TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count()); TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count()); // does not upload without peers - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_upload == time_point(seconds(0))); // does not download in seeding mode - TEST_CHECK(st.last_download == start_time); + TEST_CHECK(st.last_download == time_point(seconds(0))); } return false; }); @@ -237,7 +233,6 @@ TORRENT_TEST(finish_time_shift_active) { bool ran_to_completion = false; - lt::time_point32 start_time; lt::torrent_handle handle; seconds expected_active_duration = seconds(1); bool tick_is_in_active_range = false; @@ -252,13 +247,12 @@ TORRENT_TEST(finish_time_shift_active) if (auto ta = alert_cast(a)) { TEST_CHECK(!handle.is_valid()); - start_time = time_now(); handle = ta->handle; torrent_status st = handle.status(); // test last upload and download state before wo go throgh // torrent states - TEST_CHECK(st.last_download == start_time); - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_download == time_point(seconds(0))); + TEST_CHECK(st.last_upload == time_point(seconds(0))); } } // terminate @@ -298,9 +292,9 @@ TORRENT_TEST(finish_time_shift_active) TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count()); TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count()); // does not upload without peers - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_upload == time_point(seconds(0))); // does not download in seeding mode - TEST_CHECK(st.last_download == start_time); + TEST_CHECK(st.last_download == time_point(seconds(0))); } return false; }); @@ -311,7 +305,6 @@ TORRENT_TEST(finish_time_shift_paused) { bool ran_to_completion = false; - lt::time_point32 start_time; lt::torrent_handle handle; seconds expected_active_duration = seconds(1); bool tick_is_in_active_range = false; @@ -326,13 +319,12 @@ TORRENT_TEST(finish_time_shift_paused) if (auto ta = alert_cast(a)) { TEST_CHECK(!handle.is_valid()); - start_time = time_now(); handle = ta->handle; torrent_status st = handle.status(); // test last upload and download state before wo go throgh // torrent states - TEST_CHECK(eq(st.last_download, start_time)); - TEST_CHECK(eq(st.last_upload, start_time)); + TEST_CHECK(st.last_upload == time_point(seconds(0))); + TEST_CHECK(st.last_download == time_point(seconds(0))); } } // terminate @@ -374,9 +366,9 @@ TORRENT_TEST(finish_time_shift_paused) TEST_EQUAL(st.seeding_duration.count(), expected_active_duration.count()); TEST_EQUAL(st.finished_duration.count(), expected_active_duration.count()); // does not upload without peers - TEST_CHECK(st.last_upload == start_time); + TEST_CHECK(st.last_upload == time_point(seconds(0))); // does not download in seeding mode - TEST_CHECK(st.last_download == start_time); + TEST_CHECK(st.last_download == time_point(seconds(0))); } return false; }); diff --git a/src/block_cache.cpp b/src/block_cache.cpp index e600dcdfd..3c7fd0f83 100644 --- a/src/block_cache.cpp +++ b/src/block_cache.cpp @@ -766,7 +766,7 @@ cached_piece_entry* block_cache::add_dirty_block(disk_io_job* j) // (since these blocks now are part of the read cache) the refcounts of the // blocks are also decremented by this function. They are expected to have been // incremented by the caller. -void block_cache::blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed) +bool block_cache::blocks_flushed(cached_piece_entry* pe, int const* flushed, int num_flushed) { TORRENT_PIECE_ASSERT(pe->in_use, pe); @@ -790,7 +790,7 @@ void block_cache::blocks_flushed(cached_piece_entry* pe, int const* flushed, int pe->num_dirty -= num_flushed; update_cache_state(pe); - maybe_free_piece(pe); + return maybe_free_piece(pe); } std::pair block_cache::all_pieces() const diff --git a/src/disk_io_thread.cpp b/src/disk_io_thread.cpp index 6fe310bd8..5870da0bc 100644 --- a/src/disk_io_thread.cpp +++ b/src/disk_io_thread.cpp @@ -62,9 +62,10 @@ POSSIBILITY OF SUCH DAMAGE. #define DEBUG_DISK_THREAD 0 #if DEBUG_DISK_THREAD -#include +#include // for va_list #include #include // for vsnprintf + #define DLOG(...) debug_log(__VA_ARGS__) #else #define DLOG(...) do {} while(false) @@ -727,7 +728,7 @@ constexpr disk_job_flags_t disk_interface::cache_hit; // It is necessary to call this function with the blocks produced by // build_iovec, to reset their state to not being flushed anymore // the cache needs to be locked when calling this function - void disk_io_thread::iovec_flushed(cached_piece_entry* pe + bool disk_io_thread::iovec_flushed(cached_piece_entry* pe , int* flushing, int const num_blocks, int const block_offset , storage_error const& error , jobqueue_t& completed_jobs) @@ -742,7 +743,8 @@ constexpr disk_job_flags_t disk_interface::cache_hit; DLOG("%d ", flushing[i]); DLOG("]\n"); #endif - m_disk_cache.blocks_flushed(pe, flushing, num_blocks); + if (m_disk_cache.blocks_flushed(pe, flushing, num_blocks)) + return true; if (error) { @@ -770,6 +772,8 @@ constexpr disk_job_flags_t disk_interface::cache_hit; j = next; } } + + return false; } // issues write operations for blocks in the given @@ -802,9 +806,8 @@ constexpr disk_job_flags_t disk_interface::cache_hit; flush_iovec(pe, iov, flushing, iov_len, error); } - iovec_flushed(pe, flushing.data(), iov_len, 0, error, completed_jobs); - - m_disk_cache.maybe_free_piece(pe); + if (!iovec_flushed(pe, flushing.data(), iov_len, 0, error, completed_jobs)) + m_disk_cache.maybe_free_piece(pe); // if the cache is under high pressure, we need to evict // the blocks we just flushed to make room for more write pieces diff --git a/src/read_resume_data.cpp b/src/read_resume_data.cpp index d80a229d3..02dedb7ac 100644 --- a/src/read_resume_data.cpp +++ b/src/read_resume_data.cpp @@ -121,6 +121,9 @@ namespace { ret.last_seen_complete = std::time_t(rd.dict_find_int_value("last_seen_complete")); + ret.last_download = rd.dict_find_int_value("last_download", 0); + ret.last_upload = rd.dict_find_int_value("last_upload", 0); + // scrape data cache ret.num_complete = int(rd.dict_find_int_value("num_complete", -1)); ret.num_incomplete = int(rd.dict_find_int_value("num_incomplete", -1)); diff --git a/src/session_handle.cpp b/src/session_handle.cpp index 133b2e81e..314c9c9e9 100644 --- a/src/session_handle.cpp +++ b/src/session_handle.cpp @@ -308,6 +308,8 @@ namespace { atp.seeding_time = resume_data.seeding_time; atp.last_seen_complete = resume_data.last_seen_complete; + atp.last_upload = resume_data.last_upload; + atp.last_download = resume_data.last_download; atp.url = resume_data.url; atp.uuid = resume_data.uuid; diff --git a/src/session_impl.cpp b/src/session_impl.cpp index 713170028..fbeec2100 100644 --- a/src/session_impl.cpp +++ b/src/session_impl.cpp @@ -97,6 +97,8 @@ POSSIBILITY OF SUCH DAMAGE. // for logging stat layout #include "libtorrent/stat.hpp" +#include // for va_list + // for logging the size of DHT structures #ifndef TORRENT_DISABLE_DHT #include diff --git a/src/torrent.cpp b/src/torrent.cpp index 91b6bca3a..479ca23a7 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -181,6 +181,8 @@ bool is_downloading_state(int const st) , bool const session_paused , add_torrent_params const& p) : torrent_hot_members(ses, p, session_paused) + , m_total_uploaded(p.total_uploaded) + , m_total_downloaded(p.total_downloaded) , m_tracker_timer(ses.get_io_service()) , m_inactivity_timer(ses.get_io_service()) , m_trackerid(p.trackerid) @@ -224,6 +226,8 @@ bool is_downloading_state(int const st) , m_announce_to_dht(!(p.flags & torrent_flags::paused)) , m_ssl_torrent(false) , m_deleted(false) + , m_last_download(seconds32(p.last_download)) + , m_last_upload(seconds32(p.last_upload)) , m_auto_managed(p.flags & torrent_flags::auto_managed) , m_current_gauge_state(static_cast(no_gauge_state)) , m_moving_storage(false) @@ -2968,7 +2972,7 @@ bool is_downloading_state(int const st) { TORRENT_ASSERT(is_single_thread()); #ifndef TORRENT_NO_DEPRECATE - m_last_scrape = std::int16_t(m_ses.session_time()); + m_last_scrape = aux::time_now32(); #endif if (m_trackers.empty()) return; @@ -3151,7 +3155,7 @@ bool is_downloading_state(int const st) #ifndef TORRENT_NO_DEPRECATE if (resp.complete >= 0 && resp.incomplete >= 0) - m_last_scrape = std::int16_t(m_ses.session_time()); + m_last_scrape = aux::time_now32(); #endif #ifndef TORRENT_DISABLE_LOGGING @@ -6110,6 +6114,8 @@ bool is_downloading_state(int const st) ret.finished_time = static_cast(total_seconds(finished_time())); ret.seeding_time = static_cast(total_seconds(seeding_time())); ret.last_seen_complete = m_last_seen_complete; + ret.last_upload = total_seconds(m_last_upload.time_since_epoch()); + ret.last_download = total_seconds(m_last_download.time_since_epoch()); ret.num_complete = m_complete; ret.num_incomplete = m_incomplete; @@ -8296,14 +8302,7 @@ bool is_downloading_state(int const st) if (a < b) return 0; return std::uint16_t(a - b); } -#ifndef TORRENT_NO_DEPRECATE - std::int16_t clamped_subtract_s16(int a, int b) - { - if (a + std::numeric_limits::min() < b) - return std::numeric_limits::min(); - return std::int16_t(a - b); - } -#endif + } // anonymous namespace // this is called every time the session timer takes a step back. Since the @@ -8324,10 +8323,6 @@ bool is_downloading_state(int const st) pe->last_connected = clamped_subtract_u16(pe->last_connected, seconds); } } - -#ifndef TORRENT_NO_DEPRECATE - m_last_scrape = clamped_subtract_s16(m_last_scrape, seconds); -#endif } // the higher seed rank, the more important to seed @@ -10637,8 +10632,7 @@ bool is_downloading_state(int const st) st->completed_time = m_completed_time; #ifndef TORRENT_NO_DEPRECATE - st->last_scrape = m_last_scrape == std::numeric_limits::min() ? -1 - : clamped_subtract_u16(m_ses.session_time(), m_last_scrape); + st->last_scrape = static_cast(total_seconds(aux::time_now32() - m_last_scrape)); #endif #ifndef TORRENT_NO_DEPRECATE @@ -10667,10 +10661,12 @@ bool is_downloading_state(int const st) st->active_time = int(total_seconds(active_time())); st->seeding_time = int(total_seconds(seeding_time())); - st->time_since_upload = int(total_seconds(aux::time_now() - - m_last_upload)); - st->time_since_download = int(total_seconds(aux::time_now() - - m_last_download)); + time_point32 const unset{seconds32(0)}; + + st->time_since_upload = m_last_upload == unset ? -1 + : static_cast(total_seconds(aux::time_now32() - m_last_upload)); + st->time_since_download = m_last_download == unset ? -1 + : static_cast(total_seconds(aux::time_now32() - m_last_download)); #endif st->finished_duration = finished_time(); diff --git a/src/write_resume_data.cpp b/src/write_resume_data.cpp index 8a9093740..b728f19ab 100644 --- a/src/write_resume_data.cpp +++ b/src/write_resume_data.cpp @@ -63,6 +63,8 @@ namespace libtorrent { ret["finished_time"] = atp.finished_time; ret["seeding_time"] = atp.seeding_time; ret["last_seen_complete"] = atp.last_seen_complete; + ret["last_download"] = atp.last_download; + ret["last_upload"] = atp.last_upload; ret["num_complete"] = atp.num_complete; ret["num_incomplete"] = atp.num_incomplete; diff --git a/test/test_resume.cpp b/test/test_resume.cpp index 2f226dfbd..ce2d251a4 100644 --- a/test/test_resume.cpp +++ b/test/test_resume.cpp @@ -111,6 +111,8 @@ std::vector generate_resume_data(torrent_info* ti rd["super_seeding"] = 0; rd["added_time"] = 1347; rd["completed_time"] = 1348; + rd["last_download"] = 2; + rd["last_upload"] = 3; rd["finished_time"] = 1352; if (file_priorities && file_priorities[0]) { @@ -208,6 +210,13 @@ void default_tests(torrent_status const& s) #ifndef TORRENT_NO_DEPRECATE TEST_CHECK(s.active_time >= 1339); TEST_CHECK(s.active_time < 1339 + 10); + + auto const now = duration_cast(clock_type::now().time_since_epoch()).count(); + TEST_CHECK(s.time_since_download >= now - 2); + TEST_CHECK(s.time_since_upload >= now - 3); + + TEST_CHECK(s.time_since_download < now - 2 + 10); + TEST_CHECK(s.time_since_upload < now - 3 + 10); #endif using lt::seconds;