support adding torrents by url to a .torrent file

This commit is contained in:
Arvid Norberg 2010-12-30 01:47:30 +00:00
parent ae41b2598b
commit 26053e4b76
11 changed files with 201 additions and 18 deletions

View File

@ -1,4 +1,5 @@
* supporr CDATA tags in xml parser * support adding torrents by url to the .torrent file
* support CDATA tags in xml parser
* use a python python dictionary for settings instead of session_settings object (in python bindings) * use a python python dictionary for settings instead of session_settings object (in python bindings)
* optimized metadata transfer (magnet link) startup time (shaved off about 1 second) * optimized metadata transfer (magnet link) startup time (shaved off about 1 second)
* optimized swarm startup time (shaved off about 1 second) * optimized swarm startup time (shaved off about 1 second)

View File

@ -187,6 +187,10 @@ namespace
p.upload_mode = params["share_mode"]; p.upload_mode = params["share_mode"];
if (params.has_key("override_resume_data")) if (params.has_key("override_resume_data"))
p.override_resume_data = params["override_resume_data"]; p.override_resume_data = params["override_resume_data"];
if (params.has_key("trackerid"))
p.trackerid = extract<std::string>(params["trackerid"]);
if (params.has_key("url"))
p.url = extract<std::string>(params["url"]);
#ifndef BOOST_NO_EXCEPTIONS #ifndef BOOST_NO_EXCEPTIONS
return s.add_torrent(p); return s.add_torrent(p);

View File

@ -384,6 +384,7 @@ add_torrent()
std::vector<boost::uint8_t> const* file_priorities; std::vector<boost::uint8_t> const* file_priorities;
bool share_mode; bool share_mode;
std::string trackerid; std::string trackerid;
std::string url;
}; };
torrent_handle add_torrent(add_torrent_params const& params); torrent_handle add_torrent(add_torrent_params const& params);
@ -396,9 +397,10 @@ object with all the parameters.
The overload that does not take an ``error_code`` throws an exception on The overload that does not take an ``error_code`` throws an exception on
error and is not available when building without exception support. error and is not available when building without exception support.
The only mandatory parameter is ``save_path`` which is the directory where you The only mandatory parameters are ``save_path`` which is the directory where you
want the files to be saved. You also need to specify either the ``ti`` (the want the files to be saved. You also need to specify either the ``ti`` (the
torrent file) or ``info_hash`` (the info hash of the torrent). If you specify the torrent file), the ``info_hash`` (the info hash of the torrent) or the ``url``
(the URL to where to download the .torrent file from). If you specify the
info-hash, the torrent file will be downloaded from peers, which requires them to info-hash, the torrent file will be downloaded from peers, which requires them to
support the metadata extension. For the metadata extension to work, libtorrent must support the metadata extension. For the metadata extension to work, libtorrent must
be built with extensions enabled (``TORRENT_DISABLE_EXTENSIONS`` must not be be built with extensions enabled (``TORRENT_DISABLE_EXTENSIONS`` must not be
@ -410,6 +412,11 @@ If the torrent doesn't have a tracker, but relies on the DHT to find peers, the
``tracker_url`` can be 0, otherwise you might specify a tracker url that tracks this ``tracker_url`` can be 0, otherwise you might specify a tracker url that tracks this
torrent. torrent.
If you specify a ``url``, the torrent will be set in ``downloading_metadata`` state
until the .torrent file has been downloaded. If there's any error while downloading,
the torrent will be stopped and the torrent error state (``torrent_status::error``)
will indicate what went wrong. The ``url`` may also refer to a magnet link.
If the torrent you are trying to add already exists in the session (is either queued If the torrent you are trying to add already exists in the session (is either queued
for checking, being checked or downloading) ``add_torrent()`` will throw for checking, being checked or downloading) ``add_torrent()`` will throw
libtorrent_exception_ which derives from ``std::exception`` unless ``duplicate_is_error`` libtorrent_exception_ which derives from ``std::exception`` unless ``duplicate_is_error``
@ -5448,6 +5455,9 @@ the ``add_torrent_params``, ``p``. See `add_torrent()`_.
The overload that does not take an ``error_code`` throws an exception on The overload that does not take an ``error_code`` throws an exception on
error and is not available when building without exception support. error and is not available when building without exception support.
A simpler way to add a magnet link to a session is to pass in the
link through ``add_torrent_params::url`` argument to ``session::add_torrent()``.
For more information about magnet links, see `magnet links`_. For more information about magnet links, see `magnet links`_.
make_magnet_uri() make_magnet_uri()

View File

@ -797,7 +797,7 @@ int main(int argc, char* argv[])
{ {
if (argc == 1) if (argc == 1)
{ {
fprintf(stderr, "usage: client_test [OPTIONS] [TORRENT|MAGNETURL]\n\n" fprintf(stderr, "usage: client_test [OPTIONS] [TORRENT|MAGNETURL|URL]\n\n"
"OPTIONS:\n" "OPTIONS:\n"
" -f <log file> logs all events to the given file\n" " -f <log file> logs all events to the given file\n"
" -o <limit> limits the number of simultaneous\n" " -o <limit> limits the number of simultaneous\n"
@ -848,7 +848,8 @@ int main(int argc, char* argv[])
" " " "
"\n\n" "\n\n"
"TORRENT is a path to a .torrent file\n" "TORRENT is a path to a .torrent file\n"
"MAGNETURL is a magnet: url\n") "MAGNETURL is a magnet link\n"
"URL is a url to a torrent file\n")
; ;
return 0; return 0;
} }
@ -1108,16 +1109,19 @@ int main(int argc, char* argv[])
for (std::vector<std::string>::iterator i = torrents.begin() for (std::vector<std::string>::iterator i = torrents.begin()
, end(torrents.end()); i != end; ++i) , end(torrents.end()); i != end; ++i)
{ {
// first see if this is a torrentless download if (std::strstr(i->c_str(), "http://") == i->c_str()
if (std::strstr(i->c_str(), "magnet:") == i->c_str()) || std::strstr(i->c_str(), "https://") == i->c_str()
|| std::strstr(i->c_str(), "magnet:") == i->c_str())
{ {
add_torrent_params p; add_torrent_params p;
p.share_mode = share_mode; p.share_mode = share_mode;
p.save_path = save_path; p.save_path = save_path;
p.storage_mode = (storage_mode_t)allocation_mode; p.storage_mode = (storage_mode_t)allocation_mode;
printf("adding MANGET link: %s\n", i->c_str()); p.url = *i;
printf("adding URL: %s\n", i->c_str());
error_code ec; error_code ec;
torrent_handle h = add_magnet_uri(ses, i->c_str(), p, ec); torrent_handle h = ses.add_torrent(p, ec);
if (ec) if (ec)
{ {
fprintf(stderr, "%s\n", ec.message().c_str()); fprintf(stderr, "%s\n", ec.message().c_str());

View File

@ -85,6 +85,7 @@ namespace libtorrent
std::vector<boost::uint8_t> const* file_priorities; std::vector<boost::uint8_t> const* file_priorities;
bool share_mode; bool share_mode;
std::string trackerid; std::string trackerid;
std::string url;
}; };
} }

View File

@ -80,6 +80,8 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent namespace libtorrent
{ {
struct http_parser;
#if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING #if defined TORRENT_VERBOSE_LOGGING || defined TORRENT_LOGGING || defined TORRENT_ERROR_LOGGING
struct logger; struct logger;
#endif #endif
@ -108,7 +110,8 @@ namespace libtorrent
public: public:
torrent(aux::session_impl& ses, tcp::endpoint const& net_interface torrent(aux::session_impl& ses, tcp::endpoint const& net_interface
, int block_size, int seq, add_torrent_params const& p); , int block_size, int seq, add_torrent_params const& p
, sha1_hash const& info_hash);
~torrent(); ~torrent();
#ifndef TORRENT_DISABLE_ENCRYPTION #ifndef TORRENT_DISABLE_ENCRYPTION
@ -122,6 +125,8 @@ namespace libtorrent
// starts the announce timer // starts the announce timer
void start(); void start();
void start_download_url();
#ifndef TORRENT_DISABLE_EXTENSIONS #ifndef TORRENT_DISABLE_EXTENSIONS
void add_extension(boost::shared_ptr<torrent_plugin>); void add_extension(boost::shared_ptr<torrent_plugin>);
void add_extension(boost::function<boost::shared_ptr<torrent_plugin>(torrent*, void*)> const& ext void add_extension(boost::function<boost::shared_ptr<torrent_plugin>(torrent*, void*)> const& ext
@ -176,6 +181,7 @@ namespace libtorrent
, peer_request p); , peer_request p);
void on_disk_cache_complete(int ret, disk_io_job const& j); void on_disk_cache_complete(int ret, disk_io_job const& j);
void set_progress_ppm(int p) { m_progress_ppm = p; }
struct read_piece_struct struct read_piece_struct
{ {
boost::shared_array<char> piece_data; boost::shared_array<char> piece_data;
@ -761,6 +767,9 @@ namespace libtorrent
// a return value of false indicates an error // a return value of false indicates an error
bool set_metadata(char const* metadata_buf, int metadata_size); bool set_metadata(char const* metadata_buf, int metadata_size);
void on_torrent_download(error_code const& ec, http_parser const& parser
, char const* data, int size);
int sequence_number() const { return m_sequence_number; } int sequence_number() const { return m_sequence_number; }
bool seed_mode() const { return m_seed_mode; } bool seed_mode() const { return m_seed_mode; }
@ -964,6 +973,14 @@ namespace libtorrent
std::string m_save_path; std::string m_save_path;
// if we don't have the metadata, this is a url to
// the torrent file
std::string m_url;
// this is used as temporary storage while downloading
// the .torrent file from m_url
std::vector<char> m_torrent_file_buf;
// each bit represents a piece. a set bit means // each bit represents a piece. a set bit means
// the piece has had its hash verified. This // the piece has had its hash verified. This
// is only used in seed mode (when m_seed_mode // is only used in seed mode (when m_seed_mode

View File

@ -198,6 +198,7 @@ namespace libtorrent { namespace
{ {
m_metadata_progress += received; m_metadata_progress += received;
m_metadata_size = total_size; m_metadata_size = total_size;
m_torrent.set_progress_ppm(boost::int64_t(m_metadata_progress) * 1000000 / m_metadata_size);
} }
void on_piece_pass(int) void on_piece_pass(int)

View File

@ -72,6 +72,7 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/kademlia/dht_tracker.hpp" #include "libtorrent/kademlia/dht_tracker.hpp"
#include "libtorrent/natpmp.hpp" #include "libtorrent/natpmp.hpp"
#include "libtorrent/upnp.hpp" #include "libtorrent/upnp.hpp"
#include "libtorrent/magnet_uri.hpp"
using boost::shared_ptr; using boost::shared_ptr;
using boost::weak_ptr; using boost::weak_ptr;
@ -481,6 +482,13 @@ namespace libtorrent
#ifndef BOOST_NO_EXCEPTIONS #ifndef BOOST_NO_EXCEPTIONS
torrent_handle session::add_torrent(add_torrent_params const& params) torrent_handle session::add_torrent(add_torrent_params const& params)
{ {
if (string_begins_no_case("magnet:", params.url.c_str()))
{
add_torrent_params p(params);
p.url.clear();
return add_magnet_uri(*this, params.url, p);
}
error_code ec; error_code ec;
TORRENT_SYNC_CALL_RET2(torrent_handle, add_torrent, params, ec); TORRENT_SYNC_CALL_RET2(torrent_handle, add_torrent, params, ec);
if (ec) throw libtorrent_exception(ec); if (ec) throw libtorrent_exception(ec);
@ -490,6 +498,13 @@ namespace libtorrent
torrent_handle session::add_torrent(add_torrent_params const& params, error_code& ec) torrent_handle session::add_torrent(add_torrent_params const& params, error_code& ec)
{ {
if (string_begins_no_case("magnet:", params.url.c_str()))
{
add_torrent_params p(params);
p.url.clear();
return add_magnet_uri(*this, params.url, p, ec);
}
TORRENT_SYNC_CALL_RET2(torrent_handle, add_torrent, params, ec); TORRENT_SYNC_CALL_RET2(torrent_handle, add_torrent, params, ec);
return r; return r;
} }

View File

@ -3453,7 +3453,18 @@ namespace aux {
// figure out the info hash of the torrent // figure out the info hash of the torrent
sha1_hash const* ih = 0; sha1_hash const* ih = 0;
sha1_hash tmp;
if (params.ti) ih = &params.ti->info_hash(); if (params.ti) ih = &params.ti->info_hash();
else if (!params.url.empty())
{
// in order to avoid info-hash collisions, for
// torrents where we don't have an info-hash, but
// just a URL, set the temporary info-hash to the
// hash of the URL. This will be changed once we
// have the actual .torrent file
tmp = hasher(&params.url[0], params.url.size()).final();
ih = &tmp;
}
else ih = &params.info_hash; else ih = &params.info_hash;
// is the torrent already active? // is the torrent already active?
@ -3476,7 +3487,7 @@ namespace aux {
} }
torrent_ptr.reset(new torrent(*this, m_listen_interface torrent_ptr.reset(new torrent(*this, m_listen_interface
, 16 * 1024, queue_pos, params)); , 16 * 1024, queue_pos, params, *ih));
torrent_ptr->start(); torrent_ptr->start();
#ifndef TORRENT_DISABLE_EXTENSIONS #ifndef TORRENT_DISABLE_EXTENSIONS

View File

@ -78,6 +78,8 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/kademlia/dht_tracker.hpp" #include "libtorrent/kademlia/dht_tracker.hpp"
#include "libtorrent/peer_info.hpp" #include "libtorrent/peer_info.hpp"
#include "libtorrent/enum_net.hpp" #include "libtorrent/enum_net.hpp"
#include "libtorrent/http_connection.hpp"
#include "libtorrent/gzip.hpp" // for inflate_gzip
#ifdef TORRENT_USE_OPENSSL #ifdef TORRENT_USE_OPENSSL
#include "libtorrent/ssl_stream.hpp" #include "libtorrent/ssl_stream.hpp"
@ -321,18 +323,20 @@ namespace libtorrent
, tcp::endpoint const& net_interface , tcp::endpoint const& net_interface
, int block_size , int block_size
, int seq , int seq
, add_torrent_params const& p) , add_torrent_params const& p
, sha1_hash const& info_hash)
: m_policy(this) : m_policy(this)
, m_total_uploaded(0) , m_total_uploaded(0)
, m_total_downloaded(0) , m_total_downloaded(0)
, m_started(time_now()) , m_started(time_now())
, m_torrent_file(p.ti ? p.ti : new torrent_info(p.info_hash)) , m_torrent_file(p.ti ? p.ti : new torrent_info(info_hash))
, m_storage(0) , m_storage(0)
, m_tracker_timer(ses.m_io_service) , m_tracker_timer(ses.m_io_service)
, m_ses(ses) , m_ses(ses)
, m_trackers(m_torrent_file->trackers()) , m_trackers(m_torrent_file->trackers())
, m_save_path(complete(p.save_path))
, m_trackerid(p.trackerid) , m_trackerid(p.trackerid)
, m_save_path(complete(p.save_path))
, m_url(p.url)
, m_storage_constructor(p.storage) , m_storage_constructor(p.storage)
, m_ratio(0.f) , m_ratio(0.f)
, m_available_free_upload(0) , m_available_free_upload(0)
@ -427,6 +431,7 @@ namespace libtorrent
INVARIANT_CHECK; INVARIANT_CHECK;
if (p.name && !p.ti) m_name.reset(new std::string(p.name)); if (p.name && !p.ti) 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) if (p.tracker_url && std::strlen(p.tracker_url) > 0)
{ {
@ -437,6 +442,95 @@ namespace libtorrent
} }
} }
// since this download is not bottled, this callback will
// be called every time we receive another piece of the
// .torrent file
void torrent::on_torrent_download(error_code const& ec
, http_parser const& parser
, char const* data, int size)
{
if (ec && ec != asio::error::eof)
{
set_error(ec, m_url);
pause();
return;
}
if (size > 0)
{
m_torrent_file_buf.insert(m_torrent_file_buf.end(), data, data + size);
if (parser.content_length() > 0)
set_progress_ppm(boost::int64_t(m_torrent_file_buf.size())
* 1000000 / parser.content_length());
}
if (!ec) return;
std::string const& encoding = parser.header("content-encoding");
if ((encoding == "gzip" || encoding == "x-gzip") && m_torrent_file_buf.size())
{
std::vector<char> buf;
std::string error;
if (inflate_gzip(&m_torrent_file_buf[0], m_torrent_file_buf.size()
, buf, 4 * 1024 * 1024, error))
{
set_error(errors::http_failed_decompress, m_url);
pause();
std::vector<char>().swap(m_torrent_file_buf);
return;
}
m_torrent_file_buf.swap(buf);
}
// we're done!
error_code e;
intrusive_ptr<torrent_info> tf(new torrent_info(
&m_torrent_file_buf[0], m_torrent_file_buf.size(), e));
if (e)
{
set_error(e, m_url);
pause();
std::vector<char>().swap(m_torrent_file_buf);
return;
}
std::vector<char>().swap(m_torrent_file_buf);
// update our torrent_info object and move the
// torrent from the old info-hash to the new one
// as we replace the torrent_info object
#ifdef TORRENT_DEBUG
int num_torrents = m_ses.m_torrents.size();
#endif
m_ses.m_torrents.erase(m_torrent_file->info_hash());
m_torrent_file = tf;
m_ses.m_torrents.insert(std::make_pair(m_torrent_file->info_hash(), shared_from_this()));
TORRENT_ASSERT(num_torrents == m_ses.m_torrents.size());
// TODO: if the user added any trackers while downloading the
// .torrent file, they are overwritten. Merge them into the
// new tracker list
m_trackers = m_torrent_file->trackers();
#ifndef TORRENT_DISABLE_ENCRYPTION
hasher h;
h.update("req2", 4);
h.update((char*)&m_torrent_file->info_hash()[0], 20);
m_obfuscated_hash = h.final();
#endif
if (m_ses.m_alerts.should_post<metadata_received_alert>())
{
m_ses.m_alerts.post_alert(metadata_received_alert(
get_handle()));
}
set_state(torrent_status::downloading);
m_override_resume_data = true;
init();
announce_with_tracker();
}
void torrent::start() void torrent::start()
{ {
TORRENT_ASSERT(m_ses.is_network_thread()); TORRENT_ASSERT(m_ses.is_network_thread());
@ -472,10 +566,15 @@ namespace libtorrent
} }
} }
// we need to start announcing since we don't have any if (!m_torrent_file->is_valid() && !m_url.empty())
// metadata. To receive peers to ask for it.
if (m_torrent_file->is_valid())
{ {
// we need to download the .torrent file from m_url
start_download_url();
}
else if (m_torrent_file->is_valid())
{
// we need to start announcing since we don't have any
// metadata. To receive peers to ask for it.
init(); init();
} }
else else
@ -485,6 +584,18 @@ namespace libtorrent
} }
} }
void torrent::start_download_url()
{
TORRENT_ASSERT(!m_url.empty());
TORRENT_ASSERT(!m_torrent_file->is_valid());
boost::shared_ptr<http_connection> conn(
new http_connection(m_ses.m_io_service, m_ses.m_half_open
, boost::bind(&torrent::on_torrent_download, shared_from_this()
, _1, _2, _3, _4), false));
conn->get(m_url);
set_state(torrent_status::downloading_metadata);
}
#ifndef TORRENT_DISABLE_DHT #ifndef TORRENT_DISABLE_DHT
bool torrent::should_announce_dht() const bool torrent::should_announce_dht() const
{ {
@ -920,7 +1031,6 @@ namespace libtorrent
// ans also in the case of share mode, we need to update the priorities // ans also in the case of share mode, we need to update the priorities
update_piece_priorities(); update_piece_priorities();
std::vector<web_seed_entry> const& web_seeds = m_torrent_file->web_seeds(); std::vector<web_seed_entry> const& web_seeds = m_torrent_file->web_seeds();
m_web_seeds.insert(m_web_seeds.end(), web_seeds.begin(), web_seeds.end()); m_web_seeds.insert(m_web_seeds.end(), web_seeds.begin(), web_seeds.end());
@ -5518,10 +5628,18 @@ namespace libtorrent
m_ses.m_auto_manage_time_scaler = 2; m_ses.m_auto_manage_time_scaler = 2;
m_error = error_code(); m_error = error_code();
m_error_file.clear(); m_error_file.clear();
// if we haven't downloaded the metadata from m_url, try again
if (!m_url.empty() && !m_torrent_file->is_valid())
{
start_download_url();
return;
}
// if the error happened during initialization, try again now // if the error happened during initialization, try again now
if (!m_storage) init(); if (!m_storage) init();
if (!checking_files && should_check_files()) if (!checking_files && should_check_files())
queue_torrent_check(); queue_torrent_check();
} }
void torrent::set_error(error_code const& ec, std::string const& error_file) void torrent::set_error(error_code const& ec, std::string const& error_file)

View File

@ -162,6 +162,7 @@ namespace libtorrent { namespace
{ {
m_metadata_progress += received; m_metadata_progress += received;
m_metadata_size = total_size; m_metadata_size = total_size;
m_torrent.set_progress_ppm(boost::int64_t(m_metadata_progress) * 1000000 / m_metadata_size);
} }
void on_piece_pass(int) void on_piece_pass(int)