diff --git a/docs/manual.html b/docs/manual.html index c8d5939e4..6625c1545 100644 --- a/docs/manual.html +++ b/docs/manual.html @@ -24,146 +24,147 @@

Table of contents

-
-

write_resume_data()

+
+

save_resume_data()

-entry write_resume_data() const;
+void save_resume_data() const;
 
-

write_resume_data() generates fast-resume data and returns it as an entry. This entry +

save_resume_data() generates fast-resume data and returns it as an entry. This entry is suitable for being bencoded. For more information about how fast-resume works, see fast resume.

-

There are three cases where this function will just return an empty entry:

+

This operation is asynchronous, save_resume_data will return immediately. The resume data +is delivered when it's done through an save_resume_data_alert.

+

The fast resume data will be empty in the following cases:

  1. The torrent handle is invalid.
  2. @@ -2043,20 +2046,59 @@ not be ready to write resume data. (see libtorrent's metadata from peers extension)
-

Note that by the time this function returns, the resume data may already be invalid if the torrent +

Note that by the time you receive the fast resume data, it may already be invalid if the torrent is still downloading! The recommended practice is to first pause the torrent, then generate the -fast resume data, and then close it down. Since the disk IO is done in a separate thread, in order -to synchronize, you shoule to wait for the torrent_paused_alert before you write the resume -data.

+fast resume data, and then close it down. Make sure to not remove_torrent() before you receive +the save_resume_data_alert though. Only pause the torrent before you save the resume data +if you will remove the torrent afterwards. There's no need to pause when saving intermittent +resume data.

In full allocation mode the reume data is never invalidated by subsequent writes to the files, since pieces won't move around. This means that you don't need to pause before writing resume data in full or sparse mode. If you don't, however, any data written to -disk after you saved resume data and before the session closed is lost.

+disk after you saved resume data and before the session closed is lost.

It also means that if the resume data is out dated, libtorrent will not re-check the files, but assume that it is fairly recent. The assumption is that it's better to loose a little bit than to re-check the entire file.

It is still a good idea to save resume data periodically during download as well as when closing down.

+

Example code to pause and save resume data for all torrents and wait for the alerts:

+
+int num_resume_data = 0;
+std::vector<torrent_handle> handles = ses.get_torrents();
+for (std::vector<torrent_handle>::iterator i = handles.begin();
+        i != handles.end(); ++i)
+{
+        torrent_handle& h = *i;
+        if (!h.has_metadata()) continue;
+
+        h.pause();
+        h.save_resume_data();
+        ++num_resume_data;
+}
+
+while (num_resume_data > 0)
+{
+        alert const* a = ses.wait_for_alert(seconds(10));
+
+        // if we don't get an alert within 10 seconds, abort
+        if (a == 0) break;
+
+        std::auto_ptr<alert> holder = ses.pop_alert();
+        save_resume_data_alert const* rd = dynamic_cast<save_resume_data_alert const*>(a);
+        if (rd == 0)
+        {
+                process_alert(a);
+                continue;
+        }
+
+        torrent_handle h = rd->handle;
+        boost::filesystem::ofstream out(h.save_path()
+                / (h.get_torrent_info().name() + ".fastresume"), std::ios_base::binary);
+        out.unsetf(std::ios_base::skipws);
+        bencode(std::ostream_iterator<char>(out), *rd->resume_data);
+        --num_resume_data;
+}
+

status()

@@ -3866,6 +3908,22 @@ struct torrent_paused_alert: torrent_alert };
+
+

save_resume_data_alert

+

This alert is generated as a response to a torrent_handle::save_resume_data request. +It is generated once the disk IO thread is done writing the state for this torrent. +The resume_data member points to the resume data or is 0 on errors.

+
+struct save_resume_data_alert: torrent_alert
+{
+        save_resume_alert(torrent_handle const& h, std::string const& msg);
+
+        boost::shared_ptr<entry> resume_data;
+
+        virtual std::auto_ptr<alert> clone() const;
+};
+
+

dispatcher

The handle_alert class is defined in <libtorrent/alert.hpp>.

@@ -4051,8 +4109,8 @@ on disk. If the resume data seems to be up-to-date, return true. If not, set error to a description of what mismatched and return false.

The default storage may compare file sizes and time stamps of the files.

-
-

write_resume_data( )

+
+

write_resume_data()

 void write_resume_data(entry& rd) const = 0;
@@ -4145,7 +4203,7 @@ void delete_files() = 0;
 

fast resume

The fast resume mechanism is a way to remember which pieces are downloaded and where they are put between sessions. You can generate fast resume data by -calling torrent_handle::write_resume_data() on torrent_handle. You can +calling save_resume_data() on torrent_handle. You can then save this data to disk and use it when resuming the torrent. libtorrent will not check the piece hashes then, and rely on the information given in the fast-resume data. The fast-resume data also contains information about which @@ -4180,6 +4238,10 @@ is the number of blocks per (normal sized) piece. Usually each block is 16 * 1024 bytes in size. But if piece size is greater than 4 megabytes, the block size will increase. +pieces +A string with piece flags, one character per piece. +Bit 1 means we have that piece. + slots

list of integers. The list maps slots to piece indices. It tells which piece is on which slot. If piece index is -2 it diff --git a/docs/manual.rst b/docs/manual.rst index d21b22fed..35acfeade 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -28,7 +28,7 @@ The basic usage is as follows: * add and remove torrents from the session at run-time * save resume data for all torrent_handles (optional, see - `write_resume_data()`_) + `save_resume_data()`_) * destruct session object Each class and function is described in this manual. @@ -272,7 +272,7 @@ duplicate_torrent_ which derives from ``std::exception``. The optional parameter, ``resume_data`` can be given if up to date fast-resume data is available. The fast-resume data can be acquired from a running torrent by calling -``torrent_handle::write_resume_data()``. See `fast resume`_. +`save_resume_data()`_ on `torrent_handle`_. See `fast resume`_. The ``storage_mode`` parameter refers to the layout of the storage for this torrent. There are 3 different modes: @@ -1573,7 +1573,7 @@ Its declaration looks like this:: std::string name() const; - entry write_resume_data() const; + void save_resume_data() const; void force_reannounce() const; void force_reannounce(boost::posix_time::time_duration) const; void scrape_tracker() const; @@ -1992,17 +1992,20 @@ This must be at least 2. The default is unlimited number of connections. If -1 i function, it means unlimited. -write_resume_data() -------------------- +save_resume_data() +------------------ :: - entry write_resume_data() const; + void save_resume_data() const; -``write_resume_data()`` generates fast-resume data and returns it as an entry_. This entry_ +``save_resume_data()`` generates fast-resume data and returns it as an entry_. This entry_ is suitable for being bencoded. For more information about how fast-resume works, see `fast resume`_. -There are three cases where this function will just return an empty ``entry``: +This operation is asynchronous, ``save_resume_data`` will return immediately. The resume data +is delivered when it's done through an `save_resume_data_alert`_. + +The fast resume data will be empty in the following cases: 1. The torrent handle is invalid. 2. The torrent is checking (or is queued for checking) its storage, it will obviously @@ -2010,16 +2013,17 @@ There are three cases where this function will just return an empty ``entry``: 3. The torrent hasn't received valid metadata and was started without metadata (see libtorrent's `metadata from peers`_ extension) -Note that by the time this function returns, the resume data may already be invalid if the torrent +Note that by the time you receive the fast resume data, it may already be invalid if the torrent is still downloading! The recommended practice is to first pause the torrent, then generate the -fast resume data, and then close it down. Since the disk IO is done in a separate thread, in order -to synchronize, you shoule to wait for the ``torrent_paused_alert`` before you write the resume -data. +fast resume data, and then close it down. Make sure to not `remove_torrent()`_ before you receive +the `save_resume_data_alert`_ though. Only pause the torrent before you save the resume data +if you will remove the torrent afterwards. There's no need to pause when saving intermittent +resume data. In full allocation mode the reume data is never invalidated by subsequent writes to the files, since pieces won't move around. This means that you don't need to pause before writing resume data in full or sparse mode. If you don't, however, any data written to -disk after you saved resume data and before the session closed is lost. +disk after you saved resume data and before the session_ closed is lost. It also means that if the resume data is out dated, libtorrent will not re-check the files, but assume that it is fairly recent. The assumption is that it's better to loose a little bit than to re-check @@ -2028,6 +2032,45 @@ the entire file. It is still a good idea to save resume data periodically during download as well as when closing down. +Example code to pause and save resume data for all torrents and wait for the alerts:: + + int num_resume_data = 0; + std::vector handles = ses.get_torrents(); + for (std::vector::iterator i = handles.begin(); + i != handles.end(); ++i) + { + torrent_handle& h = *i; + if (!h.has_metadata()) continue; + + h.pause(); + h.save_resume_data(); + ++num_resume_data; + } + + while (num_resume_data > 0) + { + alert const* a = ses.wait_for_alert(seconds(10)); + + // if we don't get an alert within 10 seconds, abort + if (a == 0) break; + + std::auto_ptr holder = ses.pop_alert(); + save_resume_data_alert const* rd = dynamic_cast(a); + if (rd == 0) + { + process_alert(a); + continue; + } + + torrent_handle h = rd->handle; + boost::filesystem::ofstream out(h.save_path() + / (h.get_torrent_info().name() + ".fastresume"), std::ios_base::binary); + out.unsetf(std::ios_base::skipws); + bencode(std::ostream_iterator(out), *rd->resume_data); + --num_resume_data; + } + + status() -------- @@ -4019,6 +4062,24 @@ This is useful for synchronizing with the disk. virtual std::auto_ptr clone() const; }; +save_resume_data_alert +---------------------- + +This alert is generated as a response to a ``torrent_handle::save_resume_data`` request. +It is generated once the disk IO thread is done writing the state for this torrent. +The ``resume_data`` member points to the resume data or is 0 on errors. + +:: + + struct save_resume_data_alert: torrent_alert + { + save_resume_alert(torrent_handle const& h, std::string const& msg); + + boost::shared_ptr resume_data; + + virtual std::auto_ptr clone() const; + }; + dispatcher ---------- @@ -4235,8 +4296,8 @@ not, set ``error`` to a description of what mismatched and return false. The default storage may compare file sizes and time stamps of the files. -write_resume_data( ) --------------------- +write_resume_data() +------------------- :: @@ -4340,7 +4401,7 @@ fast resume The fast resume mechanism is a way to remember which pieces are downloaded and where they are put between sessions. You can generate fast resume data by -calling ``torrent_handle::write_resume_data()`` on torrent_handle_. You can +calling `save_resume_data()`_ on torrent_handle_. You can then save this data to disk and use it when resuming the torrent. libtorrent will not check the piece hashes then, and rely on the information given in the fast-resume data. The fast-resume data also contains information about which @@ -4374,6 +4435,9 @@ The file format is a bencoded dictionary containing the following fields: | | greater than 4 megabytes, the block size will increase. | | | | +----------------------+--------------------------------------------------------------+ +| ``pieces`` | A string with piece flags, one character per piece. | +| | Bit 1 means we have that piece. | ++----------------------+--------------------------------------------------------------+ | ``slots`` | list of integers. The list maps slots to piece indices. It | | | tells which piece is on which slot. If piece index is -2 it | | | means it is free, that there's no piece there. If it is -1, | diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 89d9fda75..73a3c4fd1 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -596,16 +596,10 @@ void scan_dir(path const& dir_path } h.pause(); - if (h.has_metadata()) - { - entry data = h.write_resume_data(); - std::stringstream s; - s << h.get_torrent_info().name() << ".fastresume"; - boost::filesystem::ofstream out(h.save_path() / s.str(), std::ios_base::binary); - out.unsetf(std::ios_base::skipws); - bencode(std::ostream_iterator(out), data); - } - ses.remove_torrent(h); + // the alert handler for save_resume_data_alert + // will save it to disk and remove the torrent + h.save_resume_data(); + handles.erase(i++); } } @@ -988,21 +982,49 @@ int main(int ac, char* av[]) { if (c == 'q') { + // keep track of the number of resume data + // alerts to wait for + int num_resume_data = 0; for (handles_t::iterator i = handles.begin(); i != handles.end(); ++i) { torrent_handle& h = i->second; if (!h.is_valid() || !h.has_metadata()) continue; + // pause + std::cout << "pausing " << h.name() << std::endl; h.pause(); + // save_resume_data will generate an alert when it's done + h.save_resume_data(); + ++num_resume_data; + } + std::cout << "waiting for resume data" << std::endl; - entry data = h.write_resume_data(); - std::stringstream s; - s << h.get_torrent_info().name() << ".fastresume"; - boost::filesystem::ofstream out(h.save_path() / s.str(), std::ios_base::binary); + while (num_resume_data > 0) + { + alert const* a = ses.wait_for_alert(seconds(10)); + if (a == 0) + { + std::cout << " aborting with " << num_resume_data << " outstanding " + "torrents to save resume data for" << std::endl; + break; + } + + std::auto_ptr holder = ses.pop_alert(); + save_resume_data_alert const* rd = dynamic_cast(a); + if (rd == 0) + { + std::cout << a->msg() << std::endl; + continue; + } + + torrent_handle h = rd->handle; + boost::filesystem::ofstream out(h.save_path() + / (h.get_torrent_info().name() + ".fastresume"), std::ios_base::binary); out.unsetf(std::ios_base::skipws); - bencode(std::ostream_iterator(out), data); - ses.remove_torrent(h); + bencode(std::ostream_iterator(out), *rd->resume_data); + std::cout << "fast resume data saved for " << h.name() << std::endl; + --num_resume_data; } break; } @@ -1085,15 +1107,9 @@ int main(int ac, char* av[]) // write resume data for the finished torrent torrent_handle h = p->handle; - entry data = h.write_resume_data(); - std::stringstream s; - s << h.get_torrent_info().name() << ".fastresume"; - boost::filesystem::ofstream out(h.save_path() / s.str(), std::ios_base::binary); - out.unsetf(std::ios_base::skipws); - bencode(std::ostream_iterator(out), data); + h.save_resume_data(); - event_string << p->handle.get_torrent_info().name() << ": " - << a->msg(); + event_string << h.name() << ": " << a->msg(); } else if (peer_error_alert* p = dynamic_cast(a.get())) { @@ -1119,6 +1135,18 @@ int main(int ac, char* av[]) { event_string << "(" << p->ip << ") " << p->msg(); } + else if (save_resume_data_alert* p = dynamic_cast(a.get())) + { + torrent_handle h = p->handle; + if (p->resume_data) + { + boost::filesystem::ofstream out(h.save_path() / (h.name() + ".fastresume"), std::ios_base::binary); + out.unsetf(std::ios_base::skipws); + bencode(std::ostream_iterator(out), *p->resume_data); + if (h.is_paused()) ses.remove_torrent(h); + } + event_string << "(" << h.name() << ") " << p->msg(); + } else if (torrent_alert* p = dynamic_cast(a.get())) { std::string name; @@ -1356,6 +1384,7 @@ int main(int ac, char* av[]) } } + std::cout << "saving session state" << std::endl; { entry session_state = ses.state(); boost::filesystem::ofstream out(".ses_state" @@ -1365,12 +1394,14 @@ int main(int ac, char* av[]) } #ifndef TORRENT_DISABLE_DHT + std::cout << "saving DHT state" << std::endl; dht_state = ses.dht_state(); boost::filesystem::ofstream out(".dht_state" , std::ios_base::binary); out.unsetf(std::ios_base::skipws); bencode(std::ostream_iterator(out), dht_state); #endif + std::cout << "closing session" << std::endl; } catch (std::exception& e) { diff --git a/include/libtorrent/alert_types.hpp b/include/libtorrent/alert_types.hpp index a6aafef6a..1dc843ede 100755 --- a/include/libtorrent/alert_types.hpp +++ b/include/libtorrent/alert_types.hpp @@ -291,6 +291,20 @@ namespace libtorrent { return std::auto_ptr(new torrent_deleted_alert(*this)); } }; + struct TORRENT_EXPORT save_resume_data_alert: torrent_alert + { + save_resume_data_alert(boost::shared_ptr const& rd + , torrent_handle const& h, std::string const& msg) + : torrent_alert(h, alert::warning, msg) + , resume_data(rd) + {} + + boost::shared_ptr resume_data; + + virtual std::auto_ptr clone() const + { return std::auto_ptr(new save_resume_data_alert(*this)); } + }; + struct TORRENT_EXPORT torrent_paused_alert: torrent_alert { torrent_paused_alert(torrent_handle const& h, std::string const& msg) diff --git a/include/libtorrent/disk_io_thread.hpp b/include/libtorrent/disk_io_thread.hpp index 17a98410e..c01393d7e 100644 --- a/include/libtorrent/disk_io_thread.hpp +++ b/include/libtorrent/disk_io_thread.hpp @@ -81,6 +81,7 @@ namespace libtorrent , delete_files , check_fastresume , check_files + , save_resume_data }; action_t action; @@ -104,6 +105,8 @@ namespace libtorrent // with lower priority int priority; + boost::shared_ptr resume_data; + // this is called when operation completes boost::function callback; }; diff --git a/include/libtorrent/storage.hpp b/include/libtorrent/storage.hpp index ec647db79..51c7b3c01 100755 --- a/include/libtorrent/storage.hpp +++ b/include/libtorrent/storage.hpp @@ -212,7 +212,7 @@ namespace libtorrent torrent_info const* info() const { return m_info.get(); } - void write_resume_data(entry& rd, std::vector const& have) const; + void write_resume_data(entry& rd) const; void async_check_fastresume(entry const* resume_data , boost::function const& handler); @@ -242,6 +242,9 @@ namespace libtorrent void async_move_storage(fs::path const& p , boost::function const& handler); + void async_save_resume_data( + boost::function const& handler); + enum return_t { // return values from check_fastresume and check_files diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 52ab67766..bfec8bb53 100755 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -178,6 +178,7 @@ namespace libtorrent void pause(); void resume(); bool is_paused() const { return m_paused; } + void save_resume_data(); void delete_files(); @@ -516,6 +517,8 @@ namespace libtorrent torrent_handle get_handle(); + void write_resume_data(entry& rd) const; + // LOGGING #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING virtual void debug_log(const std::string& line); @@ -563,6 +566,7 @@ namespace libtorrent void on_files_released(int ret, disk_io_job const& j); void on_torrent_paused(int ret, disk_io_job const& j); void on_storage_moved(int ret, disk_io_job const& j); + void on_save_resume_data(int ret, disk_io_job const& j); void on_piece_verified(int ret, disk_io_job const& j , boost::function f); diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index 57ff9b295..e389fe8d0 100755 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -315,6 +315,7 @@ namespace libtorrent bool is_paused() const; void pause() const; void resume() const; + void save_resume_data() const; #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES void resolve_countries(bool r); @@ -354,7 +355,9 @@ namespace libtorrent // to. void use_interface(const char* net_interface) const; - entry write_resume_data() const; + // use save_resume_data() instead. It is async. and + // will return the resume data in an alert + entry write_resume_data() const TORRENT_DEPRECATED; // forces this torrent to reannounce // (make a rerequest from the tracker) diff --git a/src/disk_io_thread.cpp b/src/disk_io_thread.cpp index 9fa272955..ad2530c04 100644 --- a/src/disk_io_thread.cpp +++ b/src/disk_io_thread.cpp @@ -773,13 +773,13 @@ namespace libtorrent std::string const& error_string = j.storage->error(); if (!error_string.empty()) { +#ifndef NDEBUG + std::cout << "ERROR: '" << error_string << "' " << j.error_file << std::endl; +#endif j.str = error_string; j.error_file = j.storage->error_file(); j.storage->clear_error(); ret = -1; -#ifndef NDEBUG - std::cout << "ERROR: " << error_string << " " << j.error_file << std::endl; -#endif } else { @@ -1042,6 +1042,16 @@ namespace libtorrent } break; } + case disk_io_job::save_resume_data: + { +#ifdef TORRENT_DISK_STATS + m_log << log_time() << " save resume data" << std::endl; +#endif + j.resume_data.reset(new entry(entry::dictionary_t)); + j.storage->write_resume_data(*j.resume_data); + ret = 0; + break; + } } } #ifndef BOOST_NO_EXCEPTIONS diff --git a/src/storage.cpp b/src/storage.cpp index 0d6961fe4..51fab7e12 100755 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -559,11 +559,8 @@ namespace libtorrent bool storage::write_resume_data(entry& rd) const { - if (rd.type() != entry::dictionary_t) - { - set_error("", "invalid fastresume file"); - return true; - } + TORRENT_ASSERT(rd.type() == entry::dictionary_t); + std::vector > file_sizes = get_filesizes(*m_info, m_save_path); @@ -583,7 +580,7 @@ namespace libtorrent { if (rd.type() != entry::dictionary_t) { - error = "invalid fastresume file"; + error = "invalid fastresume file (not a dictionary)"; return true; } @@ -1146,6 +1143,15 @@ namespace libtorrent { } + void piece_manager::async_save_resume_data( + boost::function const& handler) + { + disk_io_job j; + j.storage = this; + j.action = disk_io_job::save_resume_data; + m_io_thread.add_job(j, handler); + } + void piece_manager::async_release_files( boost::function const& handler) { @@ -1280,8 +1286,7 @@ namespace libtorrent return false; } - void piece_manager::write_resume_data(entry& rd - , std::vector const& have) const + void piece_manager::write_resume_data(entry& rd) const { boost::recursive_mutex::scoped_lock lock(m_mutex); @@ -1289,9 +1294,9 @@ namespace libtorrent m_storage->write_resume_data(rd); - entry::list_type& slots = rd["slots"].list(); if (m_storage_mode == storage_mode_compact) { + entry::list_type& slots = rd["slots"].list(); slots.clear(); std::vector::const_reverse_iterator last; for (last = m_slot_to_piece.rbegin(); @@ -1307,13 +1312,6 @@ namespace libtorrent slots.push_back((*i >= 0) ? *i : unassigned); } } - else - { - for (int i = 0; i < m_info->num_pieces(); ++i) - { - slots.push_back(have[i] ? i : unassigned); - } - } } void piece_manager::mark_failed(int piece_index) @@ -1710,116 +1708,122 @@ namespace libtorrent && allocation->string() != "compact") storage_mode = storage_mode_sparse; - // read piece map - entry const* slots = rd.find_key("slots"); - if (slots == 0 || slots->type() != entry::list_t) - { - error = "missing slot list"; - return check_no_fastresume(error); - } - - if ((int)slots->list().size() > m_info->num_pieces()) - { - error = "file has more slots than torrent (slots: " - + boost::lexical_cast(slots->list().size()) + " size: " - + boost::lexical_cast(m_info->num_pieces()) + " )"; - return check_no_fastresume(error); - } - // assume no piece is out of place (i.e. in a slot // other than the one it should be in) bool out_of_place = false; - - if (storage_mode == storage_mode_compact) + + // if we don't have a piece map, we need the slots + // if we're in compact mode, we also need the slots map + if (storage_mode == storage_mode_compact || rd.find_key("pieces") == 0) { - int num_pieces = int(m_info->num_pieces()); - m_slot_to_piece.resize(num_pieces, unallocated); - m_piece_to_slot.resize(num_pieces, has_no_slot); - int slot = 0; - for (entry::list_type::const_iterator i = slots->list().begin(); - i != slots->list().end(); ++i, ++slot) + // read slots map + entry const* slots = rd.find_key("slots"); + if (slots == 0 || slots->type() != entry::list_t) { - if (i->type() != entry::int_t) + error = "missing slot list"; + return check_no_fastresume(error); + } + + if ((int)slots->list().size() > m_info->num_pieces()) + { + error = "file has more slots than torrent (slots: " + + boost::lexical_cast(slots->list().size()) + " size: " + + boost::lexical_cast(m_info->num_pieces()) + " )"; + return check_no_fastresume(error); + } + + if (storage_mode == storage_mode_compact) + { + int num_pieces = int(m_info->num_pieces()); + m_slot_to_piece.resize(num_pieces, unallocated); + m_piece_to_slot.resize(num_pieces, has_no_slot); + int slot = 0; + for (entry::list_type::const_iterator i = slots->list().begin(); + i != slots->list().end(); ++i, ++slot) { - error = "invalid entry type in slot list"; - return check_no_fastresume(error); - } - - int index = int(i->integer()); - if (index >= num_pieces || index < -2) - { - error = "too high index number in slot map (index: " - + boost::lexical_cast(index) + " size: " - + boost::lexical_cast(num_pieces) + ")"; - return check_no_fastresume(error); - } - if (index >= 0) - { - m_slot_to_piece[slot] = index; - m_piece_to_slot[index] = slot; - if (slot != index) out_of_place = true; - } - else if (index == unassigned) - { - if (m_storage_mode == storage_mode_compact) - m_free_slots.push_back(slot); - } - else - { - TORRENT_ASSERT(index == unallocated); - if (m_storage_mode == storage_mode_compact) - m_unallocated_slots.push_back(slot); + if (i->type() != entry::int_t) + { + error = "invalid entry type in slot list"; + return check_no_fastresume(error); + } + + int index = int(i->integer()); + if (index >= num_pieces || index < -2) + { + error = "too high index number in slot map (index: " + + boost::lexical_cast(index) + " size: " + + boost::lexical_cast(num_pieces) + ")"; + return check_no_fastresume(error); + } + if (index >= 0) + { + m_slot_to_piece[slot] = index; + m_piece_to_slot[index] = slot; + if (slot != index) out_of_place = true; + } + else if (index == unassigned) + { + if (m_storage_mode == storage_mode_compact) + m_free_slots.push_back(slot); + } + else + { + TORRENT_ASSERT(index == unallocated); + if (m_storage_mode == storage_mode_compact) + m_unallocated_slots.push_back(slot); + } } } - } - else - { - int slot = 0; - for (entry::list_type::const_iterator i = slots->list().begin(); - i != slots->list().end(); ++i, ++slot) + else { - if (i->type() != entry::int_t) + int slot = 0; + for (entry::list_type::const_iterator i = slots->list().begin(); + i != slots->list().end(); ++i, ++slot) { - error = "invalid entry type in slot list"; - return check_no_fastresume(error); - } - - int index = int(i->integer()); - if (index != slot && index >= 0) - { - error = "invalid slot index"; - return check_no_fastresume(error); + if (i->type() != entry::int_t) + { + error = "invalid entry type in slot list"; + return check_no_fastresume(error); + } + + int index = int(i->integer()); + if (index != slot && index >= 0) + { + error = "invalid slot index"; + return check_no_fastresume(error); + } } } - } - if (!m_storage->verify_resume_data(rd, error)) - return check_no_fastresume(error); + if (!m_storage->verify_resume_data(rd, error)) + return check_no_fastresume(error); - // This will corrupt the storage - // use while debugging to find - // states that cannot be scanned - // by check_pieces. -// m_storage->shuffle(); + // This will corrupt the storage + // use while debugging to find + // states that cannot be scanned + // by check_pieces. + // m_storage->shuffle(); - if (m_storage_mode == storage_mode_compact) - { - if (m_unallocated_slots.empty()) switch_to_full_mode(); - } - else - { - TORRENT_ASSERT(m_free_slots.empty()); - TORRENT_ASSERT(m_unallocated_slots.empty()); - - if (out_of_place) + if (m_storage_mode == storage_mode_compact) { - // in this case we're in full allocation mode, but - // we're resuming a compact allocated storage - m_state = state_expand_pieces; - m_current_slot = 0; - error = "pieces needs to be reordered"; - return need_full_check; + if (m_unallocated_slots.empty()) switch_to_full_mode(); } + else + { + TORRENT_ASSERT(m_free_slots.empty()); + TORRENT_ASSERT(m_unallocated_slots.empty()); + + if (out_of_place) + { + // in this case we're in full allocation mode, but + // we're resuming a compact allocated storage + m_state = state_expand_pieces; + m_current_slot = 0; + error = "pieces needs to be reordered"; + return need_full_check; + } + } + } return check_init_storage(error); diff --git a/src/torrent.cpp b/src/torrent.cpp index a211ec3e4..324569920 100755 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -520,21 +520,18 @@ namespace libtorrent if (!fastresume_rejected) { TORRENT_ASSERT(m_resume_data.type() == entry::dictionary_t); - // parse slots - entry const* slots_ent = m_resume_data.find_key("slots"); - if (slots_ent != 0 && slots_ent->type() == entry::list_t) + + // parse have bitmask + entry const* pieces = m_resume_data.find_key("pieces"); + if (pieces && pieces->type() == entry::string_t + && pieces->string().length() == m_torrent_file->num_pieces()) { - entry::list_type const& slots = slots_ent->list(); - - for (entry::list_type::const_iterator i = slots.begin(); - i != slots.end(); ++i) + std::string const& pieces_str = pieces->string(); + for (int i = 0, end(pieces_str.size()); i < end; ++i) { - if (i->type() != entry::int_t) continue; - int piece_index = int(i->integer()); - if (piece_index < 0 || piece_index >= torrent_file().num_pieces()) - continue; - m_have_pieces[piece_index] = true; - ++m_num_pieces; + bool have = pieces_str[i] & 1; + m_have_pieces[i] = have; + m_num_pieces += have; } } @@ -557,10 +554,11 @@ namespace libtorrent if (piece_index < 0 || piece_index >= torrent_file().num_pieces()) continue; - // if this assert is hit, the resume data file was corrupt - TORRENT_ASSERT(m_have_pieces[piece_index]); - m_have_pieces[piece_index] = false; - --m_num_pieces; + if (m_have_pieces[piece_index]) + { + m_have_pieces[piece_index] = false; + --m_num_pieces; + } entry const* bitmask_ent = i->find_key("bitmask"); if (bitmask_ent == 0 || bitmask_ent->type() != entry::string_t) break; @@ -1387,6 +1385,18 @@ namespace libtorrent */ } + void torrent::on_save_resume_data(int ret, disk_io_job const& j) + { + session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); + + if (alerts().should_post(alert::warning)) + { + write_resume_data(*j.resume_data); + alerts().post_alert(save_resume_data_alert(j.resume_data + , get_handle(), "resume data generated")); + } + } + void torrent::on_torrent_paused(int ret, disk_io_job const& j) { session_impl::mutex_t::scoped_lock l(m_ses.m_mutex); @@ -2238,6 +2248,112 @@ namespace libtorrent } #endif + void torrent::write_resume_data(entry& ret) const + { + ret["file-format"] = "libtorrent resume file"; + ret["file-version"] = 1; + + ret["allocation"] = m_storage_mode == storage_mode_sparse?"sparse" + :m_storage_mode == storage_mode_allocate?"full":"compact"; + + const sha1_hash& info_hash = torrent_file().info_hash(); + ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); + + // blocks per piece + int num_blocks_per_piece = + static_cast(torrent_file().piece_length()) / block_size(); + ret["blocks per piece"] = num_blocks_per_piece; + + // if this torrent is a seed, we won't have a piece picker + // and there will be no half-finished pieces. + if (!is_seed()) + { + const std::vector& q + = m_picker->get_download_queue(); + + // unfinished pieces + ret["unfinished"] = entry::list_type(); + entry::list_type& up = ret["unfinished"].list(); + + // info for each unfinished piece + for (std::vector::const_iterator i + = q.begin(); i != q.end(); ++i) + { + if (i->finished == 0) continue; + + entry piece_struct(entry::dictionary_t); + + // the unfinished piece's index + piece_struct["piece"] = i->index; + + std::string bitmask; + const int num_bitmask_bytes + = (std::max)(num_blocks_per_piece / 8, 1); + + for (int j = 0; j < num_bitmask_bytes; ++j) + { + unsigned char v = 0; + int bits = (std::min)(num_blocks_per_piece - j*8, 8); + for (int k = 0; k < bits; ++k) + v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) + ? (1 << k) : 0; + bitmask.insert(bitmask.end(), v); + TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); + } + piece_struct["bitmask"] = bitmask; + // push the struct onto the unfinished-piece list + up.push_back(piece_struct); + } + } + + // write have bitmask + entry::string_type& pieces = ret["pieces"].string(); + pieces.resize(m_torrent_file->num_pieces()); + for (int i = 0, end(pieces.size()); i < end; ++i) + pieces[i] = m_have_pieces[i] ? 1 : 0; + + // write local peers + + entry::list_type& peer_list = ret["peers"].list(); + entry::list_type& banned_peer_list = ret["banned_peers"].list(); + + int max_failcount = m_ses.m_settings.max_failcount; + + for (policy::const_iterator i = m_policy.begin_peer() + , end(m_policy.end_peer()); i != end; ++i) + { + asio::error_code ec; + if (i->second.banned) + { + tcp::endpoint ip = i->second.ip; + entry peer(entry::dictionary_t); + peer["ip"] = ip.address().to_string(ec); + if (ec) continue; + peer["port"] = ip.port(); + banned_peer_list.push_back(peer); + continue; + } + // we cannot save remote connection + // since we don't know their listen port + // unless they gave us their listen port + // through the extension handshake + // so, if the peer is not connectable (i.e. we + // don't know its listen port) or if it has + // been banned, don't save it. + if (i->second.type == policy::peer::not_connectable) continue; + + // don't save peers that doesn't work + if (i->second.failcount >= max_failcount) continue; + + tcp::endpoint ip = i->second.ip; + entry peer(entry::dictionary_t); + peer["ip"] = ip.address().to_string(ec); + if (ec) continue; + peer["port"] = ip.port(); + peer_list.push_back(peer); + } + } + void torrent::get_full_peer_list(std::vector& v) const { v.clear(); @@ -3103,6 +3219,27 @@ namespace libtorrent } } + // this is an async operation triggered by the client + void torrent::save_resume_data() + { + INVARIANT_CHECK; + + if (m_owning_storage.get()) + { + TORRENT_ASSERT(m_storage); + m_storage->async_save_resume_data( + bind(&torrent::on_save_resume_data, shared_from_this(), _1, _2)); + } + else + { + if (alerts().should_post(alert::warning)) + { + alerts().post_alert(save_resume_data_alert(boost::shared_ptr() + , get_handle(), "save resume data failed, torrent is being destructed")); + } + } + } + void torrent::pause() { INVARIANT_CHECK; diff --git a/src/torrent_handle.cpp b/src/torrent_handle.cpp index 8ea04f327..ad827412d 100755 --- a/src/torrent_handle.cpp +++ b/src/torrent_handle.cpp @@ -253,6 +253,12 @@ namespace libtorrent TORRENT_FORWARD(pause()); } + void torrent_handle::save_resume_data() const + { + INVARIANT_CHECK; + TORRENT_FORWARD(save_resume_data()); + } + void torrent_handle::resume() const { INVARIANT_CHECK; @@ -431,131 +437,9 @@ namespace libtorrent { INVARIANT_CHECK; - boost::shared_ptr t = m_torrent.lock(); - if (!t) -#ifdef BOOST_NO_EXCEPTIONS - return entry(); -#else - throw_invalid_handle(); -#endif - session_impl::mutex_t::scoped_lock l(t->session().m_mutex); - if (!t->valid_metadata()) -#ifdef BOOST_NO_EXCEPTIONS - return entry(); -#else - throw_invalid_handle(); -#endif - - std::vector have_pieces = t->pieces(); - entry ret(entry::dictionary_t); - - ret["file-format"] = "libtorrent resume file"; - ret["file-version"] = 1; - - storage_mode_t sm = t->storage_mode(); - ret["allocation"] = sm == storage_mode_sparse?"sparse" - :sm == storage_mode_allocate?"full":"compact"; - - const sha1_hash& info_hash = t->torrent_file().info_hash(); - ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); - - // blocks per piece - int num_blocks_per_piece = - static_cast(t->torrent_file().piece_length()) / t->block_size(); - ret["blocks per piece"] = num_blocks_per_piece; - - // if this torrent is a seed, we won't have a piece picker - // and there will be no half-finished pieces. - if (!t->is_seed()) - { - const piece_picker& p = t->picker(); - - const std::vector& q - = p.get_download_queue(); - - // unfinished pieces - ret["unfinished"] = entry::list_type(); - entry::list_type& up = ret["unfinished"].list(); - - // info for each unfinished piece - for (std::vector::const_iterator i - = q.begin(); i != q.end(); ++i) - { - if (i->finished == 0) continue; - - entry piece_struct(entry::dictionary_t); - - // the unfinished piece's index - piece_struct["piece"] = i->index; - - have_pieces[i->index] = true; - - std::string bitmask; - const int num_bitmask_bytes - = (std::max)(num_blocks_per_piece / 8, 1); - - for (int j = 0; j < num_bitmask_bytes; ++j) - { - unsigned char v = 0; - int bits = (std::min)(num_blocks_per_piece - j*8, 8); - for (int k = 0; k < bits; ++k) - v |= (i->info[j*8+k].state == piece_picker::block_info::state_finished) - ? (1 << k) : 0; - bitmask.insert(bitmask.end(), v); - TORRENT_ASSERT(bits == 8 || j == num_bitmask_bytes - 1); - } - piece_struct["bitmask"] = bitmask; - // push the struct onto the unfinished-piece list - up.push_back(piece_struct); - } - } - - std::vector piece_index; - t->filesystem().write_resume_data(ret, have_pieces); - - // write local peers - - entry::list_type& peer_list = ret["peers"].list(); - entry::list_type& banned_peer_list = ret["banned_peers"].list(); - - policy& pol = t->get_policy(); - - int max_failcount = t->settings().max_failcount; - - for (policy::iterator i = pol.begin_peer() - , end(pol.end_peer()); i != end; ++i) - { - asio::error_code ec; - if (i->second.banned) - { - tcp::endpoint ip = i->second.ip; - entry peer(entry::dictionary_t); - peer["ip"] = ip.address().to_string(ec); - if (ec) continue; - peer["port"] = ip.port(); - banned_peer_list.push_back(peer); - continue; - } - // we cannot save remote connection - // since we don't know their listen port - // unless they gave us their listen port - // through the extension handshake - // so, if the peer is not connectable (i.e. we - // don't know its listen port) or if it has - // been banned, don't save it. - if (i->second.type == policy::peer::not_connectable) continue; - - // don't save peers that doesn't work - if (i->second.failcount >= max_failcount) continue; - - tcp::endpoint ip = i->second.ip; - entry peer(entry::dictionary_t); - peer["ip"] = ip.address().to_string(ec); - if (ec) continue; - peer["port"] = ip.port(); - peer_list.push_back(peer); - } + TORRENT_FORWARD(write_resume_data(ret)); + t->filesystem().write_resume_data(ret); return ret; }