diff --git a/ChangeLog b/ChangeLog index 657516e01..ad54df83b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,4 @@ + * support saving metadata in resume file, enable it by default for magnet links * support for receiving multi announce messages for local peer discovery * added session::listen_no_system_port flag to prevent libtorrent from ever binding the listen socket to port 0 * added option to not recheck on missing or incomplete resume data diff --git a/docs/manual.rst b/docs/manual.rst index 5c2614d4e..ff8f0601e 100644 --- a/docs/manual.rst +++ b/docs/manual.rst @@ -2134,6 +2134,7 @@ Its declaration looks like this:: std::string name() const; + enum save_resume_flags_t { flush_disk_cache = 1, save_info_dict = 2 }; void save_resume_data(int flags = 0) const; bool need_save_resume_data() const; void force_reannounce() const; @@ -2933,14 +2934,19 @@ save_resume_data() :: + enum save_resume_flags_t { flush_disk_cache = 1, save_info_dict = 2 }; void save_resume_data(int flags = 0) const; ``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`_. -The ``flags`` argument may be set to ``torrent_handle::flush_cache``. Doing so will flush the disk -cache before creating the resume data. This avoids a problem with file timestamps in the resume -data in case the cache hasn't been flushed yet. +The ``flags`` argument is a bitmask of flags ORed together. If the flag ``torrent_handle::flush_cache`` +is set, the disk cache will be flushed before creating the resume data. This avoids a problem with +file timestamps in the resume data in case the cache hasn't been flushed yet. + +If the flag ``torrent_handle::save_info_dict`` is set, the resume data will contain the metadata +from the torrent file as well. This is default for any torrent that's added without a torrent +file (such as a magnet link or a URL). 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`_. diff --git a/examples/client_test.cpp b/examples/client_test.cpp index 24d58141e..d1d50e905 100644 --- a/examples/client_test.cpp +++ b/examples/client_test.cpp @@ -671,7 +671,7 @@ void add_torrent(libtorrent::session& ses p.share_mode = share_mode; lazy_entry resume_data; - std::string filename = combine_path(save_path, ".resume/" + t->name() + ".resume"); + std::string filename = combine_path(save_path, ".resume/" + to_hex(t->info_hash().to_string()) + ".resume"); std::vector buf; if (load_file(filename.c_str(), buf, ec) == 0) @@ -845,7 +845,7 @@ void handle_alert(libtorrent::session& ses, libtorrent::alert* a { std::vector out; bencode(std::back_inserter(out), *p->resume_data); - save_file(combine_path(h.save_path(), ".resume/" + h.name() + ".resume"), out); + save_file(combine_path(h.save_path(), ".resume/" + to_hex(h.info_hash().to_string()) + ".resume"), out); if (non_files.find(h) == non_files.end() && std::find_if(files.begin(), files.end() , boost::bind(&handles_t::value_type::second, _1) == h) == files.end()) @@ -1211,6 +1211,26 @@ int main(int argc, char* argv[]) p.storage_mode = (storage_mode_t)allocation_mode; p.url = *i; + std::vector buf; + if (std::strstr(i->c_str(), "magnet:") == i->c_str()) + { + std::string btih = url_has_argument(*i, "xt"); + if (btih.empty()) continue; + + if (btih.compare(0, 9, "urn:btih:") != 0) + continue; + + sha1_hash info_hash; + if (btih.size() == 40 + 9) from_hex(&btih[9], 40, (char*)&info_hash[0]); + else info_hash.assign(base32decode(btih.substr(9))); + + std::string filename = combine_path(save_path, ".resume/" + + to_hex(info_hash.to_string()) + ".resume"); + + if (load_file(filename.c_str(), buf, ec) == 0) + p.resume_data = &buf; + } + printf("adding URL: %s\n", i->c_str()); error_code ec; torrent_handle h = ses.add_torrent(p, ec); @@ -1985,7 +2005,7 @@ int main(int argc, char* argv[]) torrent_handle h = rd->handle; std::vector out; bencode(std::back_inserter(out), *rd->resume_data); - save_file(combine_path(h.save_path(), ".resume/" + h.name() + ".resume"), out); + save_file(combine_path(h.save_path(), ".resume/" + to_hex(h.info_hash().to_string()) + ".resume"), out); } if (g_log_file) fclose(g_log_file); printf("\nsaving session state\n"); diff --git a/include/libtorrent/torrent.hpp b/include/libtorrent/torrent.hpp index 3406fd5c4..e758d82ac 100644 --- a/include/libtorrent/torrent.hpp +++ b/include/libtorrent/torrent.hpp @@ -1275,6 +1275,10 @@ namespace libtorrent // round-robin index into m_interfaces mutable boost::uint8_t m_interface_index; + // these are the flags sent in on a call to save_resume_data + // we need to save them to check them in write_resume_data + boost::uint8_t m_save_resume_flags; + // set to true when this torrent has been paused but // is waiting to finish all current download requests // before actually closing all connections @@ -1290,6 +1294,12 @@ namespace libtorrent // rotating sequence number for LSD announces sent out. // used to only use IP broadcast for every 8th lsd announce boost::uint8_t m_lsd_seq:3; + + // this is set to true if the torrent was started without + // metadata. It is used to save metadata in the resume file + // by default for such torrents. It does not necessarily + // have to be a magnet link. + bool m_magnet_link:1; }; } diff --git a/include/libtorrent/torrent_handle.hpp b/include/libtorrent/torrent_handle.hpp index e0a3eb286..018e286a8 100644 --- a/include/libtorrent/torrent_handle.hpp +++ b/include/libtorrent/torrent_handle.hpp @@ -230,7 +230,7 @@ namespace libtorrent void force_recheck() const; - enum save_resume_flags_t { flush_disk_cache = 1 }; + enum save_resume_flags_t { flush_disk_cache = 1, save_info_dict = 2 }; void save_resume_data(int flags = 0) const; bool need_save_resume_data() const; diff --git a/src/torrent.cpp b/src/torrent.cpp index b8a00e493..17d088f3c 100644 --- a/src/torrent.cpp +++ b/src/torrent.cpp @@ -332,11 +332,9 @@ namespace libtorrent , m_total_uploaded(0) , m_total_downloaded(0) , m_started(time_now()) - , m_torrent_file(p.ti ? p.ti : new torrent_info(info_hash)) , m_storage(0) , m_tracker_timer(ses.m_io_service) , m_ses(ses) - , m_trackers(m_torrent_file->trackers()) , m_trackerid(p.trackerid) , m_save_path(complete(p.save_path)) , m_url(p.url) @@ -358,13 +356,13 @@ namespace libtorrent , m_storage_mode(p.storage_mode) , m_announcing(false) , m_waiting_tracker(false) - , m_seed_mode(p.seed_mode && m_torrent_file->is_valid()) + , m_seed_mode(false) , m_active_time(0) , m_last_working_tracker(-1) , m_finished_time(0) , m_sequential_download(false) , m_got_tracker_response(false) - , m_connections_initialized(p.ti && p.ti->is_valid()) + , m_connections_initialized(false) , m_super_seeding(false) , m_override_resume_data(p.override_resume_data) #ifndef TORRENT_DISABLE_RESOLVE_COUNTRIES @@ -377,8 +375,7 @@ namespace libtorrent , m_max_uploads(~0) , m_deficit_counter(0) , m_num_uploads(0) - , m_block_size_shift(root2((p.ti && p.ti->is_valid()) - ? (std::min)(block_size, m_torrent_file->piece_length()) : block_size)) + , m_block_size_shift(root2(block_size)) , m_has_incoming(false) , m_files_checked(false) , m_queued_for_checking(false) @@ -403,10 +400,99 @@ namespace libtorrent , m_last_upload(0) , m_downloaders(0xffffff) , m_interface_index(0) + , m_save_resume_flags(0) , m_graceful_pause_mode(false) , m_need_connect_boost(true) , m_lsd_seq(0) + , m_magnet_link(false) { + if (!p.ti || !p.ti->is_valid()) + { + // we don't have metadata for this torrent. We'll download + // it either through the URL passed in, or through a metadata + // extension. Make sure that when we save resume data for this + // torrent, we also save the metadata + m_magnet_link = true; + + // did the user provide resume data? + // maybe the metadata is in there + if (p.resume_data) + { + int pos; + error_code ec; + lazy_entry tmp; + lazy_entry const* info = 0; +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " adding magnet link with resume data\n"; +#endif + if (lazy_bdecode(&(*p.resume_data)[0], &(*p.resume_data)[0] + + p.resume_data->size(), tmp, ec, &pos) == 0 + && tmp.type() == lazy_entry::dict_t + && (info = tmp.dict_find_dict("info"))) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " found metadata in resume data\n"; +#endif + // verify the info-hash of the metadata stored in the resume file matches + // the torrent we're loading + + std::pair buf = info->data_section(); + sha1_hash resume_ih = hasher(buf.first, buf.second).final(); + + // if url is set, the info_hash is not actually the info-hash of the + // torrent, but the hash of the URL, until we have the full torrent + if (resume_ih == info_hash || !p.url.empty()) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " info-hash matched\n"; +#endif + m_torrent_file = (p.ti ? p.ti : new torrent_info(resume_ih)); + + if (!m_torrent_file->parse_info_section(*info, ec, 0)) + { +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + (*m_ses.m_logger) << time_now_string() << " failed to load metadata from resume file: " + << ec.message() << "\n"; +#endif + } +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + else + { + (*m_ses.m_logger) << time_now_string() << " successfully loaded metadata from resume file\n"; + } +#endif + } +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + else + { + (*m_ses.m_logger) << time_now_string() << " metadata info-hash failed\n"; + } +#endif + } +#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING + else + { + (*m_ses.m_logger) << time_now_string() << " no metadata found\n"; + } +#endif + } + } + + if (!m_torrent_file) + m_torrent_file = (p.ti ? p.ti : new torrent_info(info_hash)); + + m_trackers = m_torrent_file->trackers(); + if (m_torrent_file->is_valid()) + { + m_seed_mode = p.seed_mode; + m_connections_initialized = true; + m_block_size_shift = root2((std::min)(block_size, m_torrent_file->piece_length())); + } + else + { + if (p.name) m_name.reset(new std::string(p.name)); + } + if (!m_url.empty() && m_uuid.empty()) m_uuid = m_url; TORRENT_ASSERT(m_ses.is_network_thread()); @@ -439,7 +525,6 @@ namespace libtorrent #endif INVARIANT_CHECK; - if (p.name && (!p.ti || !p.ti->is_valid())) m_name.reset(new std::string(p.name)); if (!m_name && !m_url.empty()) m_name.reset(new std::string(m_url)); if (p.tracker_url && std::strlen(p.tracker_url) > 0) @@ -4353,6 +4438,13 @@ namespace libtorrent const sha1_hash& info_hash = torrent_file().info_hash(); ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end()); + if (valid_metadata()) + { + if (m_magnet_link || (m_save_resume_flags & torrent_handle::save_info_dict)) + ret["info"] = bdecode(&torrent_file().metadata()[0] + , &torrent_file().metadata()[0] + torrent_file().metadata_size()); + } + // blocks per piece int num_blocks_per_piece = static_cast(torrent_file().piece_length()) / block_size(); @@ -5893,6 +5985,7 @@ namespace libtorrent m_need_save_resume_data = false; m_last_saved_resume = time(0); + m_save_resume_flags = boost::uint8_t(flags); TORRENT_ASSERT(m_storage); if (m_state == torrent_status::queued_for_checking