diff --git a/ChangeLog b/ChangeLog index b4ddbd5fc..f9dbf6839 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * add feature to stop torrents immediately after checking files is done * make all non-auto managed torrents exempt from queuing logic, including checking torrents. * add option to not proxy tracker connections through proxy diff --git a/bindings/python/src/session.cpp b/bindings/python/src/session.cpp index 2fd126f52..be1a7cf5a 100644 --- a/bindings/python/src/session.cpp +++ b/bindings/python/src/session.cpp @@ -616,6 +616,7 @@ void bind_session() .value("flag_sequential_download", add_torrent_params::flag_sequential_download) .value("flag_use_resume_save_path", add_torrent_params::flag_use_resume_save_path) .value("flag_merge_resume_http_seeds", add_torrent_params::flag_merge_resume_http_seeds) + .value("flag_stop_when_ready", add_torrent_params::flag_stop_when_ready) ; class_("cache_status") #ifndef TORRENT_NO_DEPRECATE diff --git a/bindings/python/src/torrent_handle.cpp b/bindings/python/src/torrent_handle.cpp index f9a4ea84c..fa8580b2f 100644 --- a/bindings/python/src/torrent_handle.cpp +++ b/bindings/python/src/torrent_handle.cpp @@ -417,6 +417,7 @@ void bind_torrent_handle() .def("is_valid", _(&torrent_handle::is_valid)) .def("pause", _(&torrent_handle::pause), arg("flags") = 0) .def("resume", _(&torrent_handle::resume)) + .def("stop_when_ready", _(&torrent_handle::stop_when_ready)) .def("clear_error", _(&torrent_handle::clear_error)) .def("set_priority", _(&torrent_handle::set_priority)) .def("super_seeding", super_seeding1) diff --git a/bindings/python/src/torrent_status.cpp b/bindings/python/src/torrent_status.cpp index 65b838a9f..4bdacb809 100644 --- a/bindings/python/src/torrent_status.cpp +++ b/bindings/python/src/torrent_status.cpp @@ -28,6 +28,7 @@ void bind_torrent_status() .def_readonly("info_hash", &torrent_status::info_hash) .def_readonly("state", &torrent_status::state) .def_readonly("paused", &torrent_status::paused) + .def_readonly("stop_when_ready", &torrent_status::stop_when_ready) .def_readonly("auto_managed", &torrent_status::auto_managed) .def_readonly("sequential_download", &torrent_status::sequential_download) .def_readonly("is_seeding", &torrent_status::is_seeding) diff --git a/include/libtorrent/add_torrent_params.hpp b/include/libtorrent/add_torrent_params.hpp index ee2f885de..cf6b16573 100644 --- a/include/libtorrent/add_torrent_params.hpp +++ b/include/libtorrent/add_torrent_params.hpp @@ -267,6 +267,11 @@ namespace libtorrent // is passed in here as well as the .torrent file. flag_merge_resume_http_seeds = 0x2000, + // the stop when ready flag. Setting this flag is equivalent to calling + // torrent_handle::stop_when_ready() immediately after the torrent is + // added. + flag_stop_when_ready = 0x4000, + // internal default_flags = flag_pinned | flag_update_subscribe | flag_auto_managed | flag_paused | flag_apply_ip_filter diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index fe7eb753f..14c00fa29 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -478,6 +478,8 @@ namespace libtorrent void set_announce_to_trackers(bool b) { m_announce_to_trackers = b; } void set_announce_to_lsd(bool b) { m_announce_to_lsd = b; } + void stop_when_ready(bool b) { m_stop_when_ready = b; } + int started() const { return m_started; } void step_session_time(int seconds); void do_pause(); @@ -521,7 +523,7 @@ namespace libtorrent // ============ end deprecation ============= void piece_availability(std::vector& avail) const; - + void set_piece_priority(int index, int priority); int piece_priority(int index) const; @@ -1700,6 +1702,10 @@ namespace libtorrent // file bool m_merge_resume_http_seeds:1; + // if this is set, whenever transitioning into a downloading/seeding state + // from a non-downloading/seeding state, the torrent is paused. + bool m_stop_when_ready:1; + #if TORRENT_USE_ASSERTS public: // set to false until we've loaded resume data diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index 5e3b3ed8c..a727ddfae 100644 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -554,6 +554,19 @@ namespace libtorrent void pause(int flags = 0) const; void resume() const; + // set or clear the stop-when-ready flag. When this flag is set, the + // torrent will *force stop* whenever it transitions from a + // non-data-transferring state into a data-transferring state (referred to + // as being ready to download or seed). This is useful for torrents that + // should not start downloading or seeding yet, but what to be made ready + // to do so. A torrent may need to have its files checked for instance, so + // it needs to be started and possibly queued for checking (auto-managed + // and started) but as soon as it's done, it should be stopped. + // + // *Force stopped* means auto-managed is set to false and it's paused. As + // if auto_manage(false) and pause() were called on the torrent. + void stop_when_ready(bool b) const; + // Explicitly sets the upload mode of the torrent. In upload mode, the // torrent will not request any pieces. If the torrent is auto managed, // it will automatically be taken out of upload mode periodically (see diff --git a/include/libtorrent/torrent_status.hpp b/include/libtorrent/torrent_status.hpp index 73af1e8fb..bfccdedd2 100644 --- a/include/libtorrent/torrent_status.hpp +++ b/include/libtorrent/torrent_status.hpp @@ -485,6 +485,11 @@ namespace libtorrent bool announcing_to_lsd; bool announcing_to_dht; + // this reflects whether the ``stop_when_ready`` flag is currently enabled + // on this torrent. For more information, see + // torrent_handle::stop_when_ready(). + bool stop_when_ready; + // the info-hash for this torrent sha1_hash info_hash; }; diff --git a/simulation/test_auto_manage.cpp b/simulation/test_auto_manage.cpp index 98ad520d6..d9653968b 100644 --- a/simulation/test_auto_manage.cpp +++ b/simulation/test_auto_manage.cpp @@ -349,7 +349,6 @@ TORRENT_TEST(seed_limit) }, [](lt::session& ses) { - // verify result (none should have been started) // make sure only 3 got started std::vector alerts; ses.pop_alerts(&alerts); @@ -439,7 +438,6 @@ TORRENT_TEST(download_limit) }, [](lt::session& ses) { - // verify result (none should have been started) // make sure only 3 got started std::vector alerts; ses.pop_alerts(&alerts); @@ -538,7 +536,6 @@ TORRENT_TEST(checking_announce) }, [](lt::session& ses) { - // verify result (none should have been started) // make sure only 3 got started std::vector alerts; ses.pop_alerts(&alerts); @@ -590,7 +587,6 @@ TORRENT_TEST(paused_checking) }, [](lt::session& ses) { - // verify result (none should have been started) // make sure only 3 got started std::vector alerts; ses.pop_alerts(&alerts); @@ -619,6 +615,71 @@ TORRENT_TEST(paused_checking) } }); } + +// set the stop_when_ready flag and make sure we receive a paused alert *before* +// a state_changed_alert +TORRENT_TEST(stop_when_ready) +{ + run_test( + [](settings_pack& sett) {}, + + [](lt::session& ses) { + // add torrents + lt::add_torrent_params params = create_torrent(0, true); + // torrents are started and auto-managed + params.flags |= add_torrent_params::flag_auto_managed; + params.flags |= add_torrent_params::flag_stop_when_ready; + // we need this to get the tracker_announce_alert + params.trackers.push_back("http://10.10.0.2/announce"); + ses.async_add_torrent(params); + }, + + [](lt::session& ses) { + // verify result (none should have been started) + std::vector alerts; + ses.pop_alerts(&alerts); + + lt::time_point start_time = alerts[0]->timestamp(); + + int num_paused = 0; + for (alert* a : alerts) + { + fprintf(stderr, "%-3d %s\n", int(duration_cast(a->timestamp() + - start_time).count()), a->message().c_str()); + + if (alert_cast(a)) + { + ++num_paused; + } + + if (state_changed_alert* sc = alert_cast(a)) + { + if (sc->state == torrent_status::seeding) + { + // once we turn into beeing a seed. we should have been paused + // already. + TEST_EQUAL(num_paused, 1); + } + } + // there should not have been any announces. the torrent should have + // been stopped *before* announcing. + TEST_EQUAL(alert_cast(a), NULL); + } + + for (torrent_handle const& h : ses.get_torrents()) + { + // the torrent should have been force-stopped (after checking was + // donw, because we set the stop_when_ready flag). Force stopped + // means not auto-managed and paused. + torrent_status st = h.status(); + TEST_CHECK(!st.auto_managed); + TEST_EQUAL(st.paused, true); + // it should be seeding. If it's not seeding it may not have had its + // files checked. + TEST_EQUAL(st.state, torrent_status::seeding); + } + }); +} // TODO: assert that the torrent_paused_alert is posted when pausing // downloading, seeding, checking torrents as well as the graceful pause // TODO: test limits of tracker, DHT and LSD announces diff --git a/src/torrent.cpp b/src/torrent.cpp index ee67dc386..979d40a18 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -279,6 +279,7 @@ namespace libtorrent , m_pending_active_change(false) , m_use_resume_save_path(p.flags & add_torrent_params::flag_use_resume_save_path) , m_merge_resume_http_seeds(p.flags & add_torrent_params::flag_merge_resume_http_seeds) + , m_stop_when_ready(p.flags & add_torrent_params::flag_stop_when_ready) { // we cannot log in the constructor, because it relies on shared_from_this // being initialized, which happens after the constructor returns. @@ -11610,6 +11611,29 @@ namespace libtorrent if (m_peer_list) m_peer_list->clear_peer_prio(); } + namespace + { + bool is_downloading_state(int st) + { + switch (st) + { + case torrent_status::checking_files: + case torrent_status::allocating: + case torrent_status::checking_resume_data: + return false; + case torrent_status::downloading_metadata: + case torrent_status::downloading: + case torrent_status::finished: + case torrent_status::seeding: + return true; + default: + // unexpected state + TORRENT_ASSERT_VAL(false, st); + return false; + } + } + } + void torrent::set_state(torrent_status::state_t s) { TORRENT_ASSERT(is_single_thread()); @@ -11643,6 +11667,21 @@ namespace libtorrent get_handle()); } + if (m_stop_when_ready + && !is_downloading_state(m_state) + && is_downloading_state(s)) + { +#ifndef TORRENT_DISABLE_LOGGING + debug_log("stop_when_ready triggered"); +#endif + // stop_when_ready is set, and we're transitioning from a downloading + // state to a non-downloading state. pause the torrent. Note that + // "downloading" is defined broadly to include any state where we + // either upload or download (for the purpose of this flag). + auto_managed(false); + pause(); + } + m_state = s; #ifndef TORRENT_DISABLE_LOGGING @@ -11742,6 +11781,7 @@ namespace libtorrent st->announcing_to_trackers = m_announce_to_trackers; st->announcing_to_lsd = m_announce_to_lsd; st->announcing_to_dht = m_announce_to_dht; + st->stop_when_ready = m_stop_when_ready; st->added_time = m_added_time; st->completed_time = m_completed_time; diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index d25f9ab00..51f4cdec6 100644 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -329,6 +329,11 @@ namespace libtorrent TORRENT_ASYNC_CALL1(pause, bool(flags & graceful_pause)); } + void torrent_handle::stop_when_ready(bool b) const + { + TORRENT_ASYNC_CALL1(stop_when_ready, b); + } + void torrent_handle::apply_ip_filter(bool b) const { TORRENT_ASYNC_CALL1(set_apply_ip_filter, b);