I'm writing a torrent client that heavily uses magnet links to start downloads, my code base started out from client_test and went from there, and since even a torrent started with a magnet link saves out a 'save_path/torrent_name.resume' on exit, it would be great if on the next run with the same magnet link, it could use that information (perhaps not even having to grab the metadata, since that was already done the first time)
Here is my modified add_torrent() method where I thought this might work (it can basically be dropped into client_test as-is):
void add_torrent(libtorrent::session& ses , handles_t& handles , std::string const& torrent , float preferred_ratio , int allocation_mode , path const& save_path , int torrent_upload_limit , int torrent_download_limit) { using namespace libtorrent;
// first see if this is a torrentless download
bool is_magnet_link = (std::strstr(torrent.c_str(), "magnet:") == torrent.c_str());
std::string name;
add_torrent_params p;
p.save_path = save_path;
p.storage_mode = (storage_mode_t)allocation_mode;
p.paused = true;
p.duplicate_is_error = false;
p.auto_managed = true;
error_code ec;
torrent_handle h;
if(is_magnet_link){
printf("adding MAGNET link: %s\n", torrent.c_str());
boost::optional<std::string> display_name = url_has_argument(torrent.c_str(), "dn");
if (display_name){
name = unescape_string(display_name->c_str(), ec);
boost::algorithm::trim(name);
boost::algorithm::replace_all(name, " ", "-");
printf("display_name of magnet link: %s\n", name.c_str());
grab_resume_data(p, name, save_path);
}
h = add_magnet_uri(ses, torrent.c_str(), p, ec);
// otherwise it's a regular torrent file
}else{
boost::intrusive_ptr<torrent_info> t;
t = new torrent_info(torrent.c_str(), ec);
if (ec)
{
fprintf(stderr, "%s: %s\n", torrent.c_str(), ec.message().c_str());
return;
}
printf("%s\n", t->name().c_str());
grab_resume_data(p, t->name(), save_path);
p.ti = t;
h = ses.add_torrent(p, ec);
}
if (ec)
{
fprintf(stderr, "failed to add torrent: %s\n", ec.message().c_str());
return;
}
handles.insert(std::pair<const std::string, torrent_handle>(std::string(), h));
h.set_max_connections(max_connections_per_torrent);
h.set_max_uploads(-1);
h.set_ratio(preferred_ratio);
h.set_upload_limit(torrent_upload_limit);
h.set_download_limit(torrent_download_limit);
}
I think the way this should be handled would be to extract the name from the magnet URL as I did above, load a .resume file if it exists, and if the info_hash from the magnet URL and the .resume file match, it's the exact same torrent and fast_resume can be used.
Going from the current documentation (which seems at least a little out of date from 0.15.5, which I'm currently using):
http://www.rasterbar.com/products/libtorrent/manual.html#fast-resume
I believe the fast resume file format would have to be added to (as opposed to modified) to hold the relevant meta-data that a .torrent file would normally hold. I think all that it is lacking is the contents of the 'info' key, and perhaps some extensions added after the fact. Can anyone confirm what would need to be added to this file.
As an afterthought, this would also support resuming a torrent when only the .resume file is available.
I'd appreciate any thoughts and pointers on the issue, and if you would accept a patch to do just this?
Comment #1
Posted on Feb 9, 2011 by Swift MonkeyI realized I modified that method more than I thought, so I'm just going to go ahead and attach the full source code of what I am working on so far, it compiles the same as client_test or simple_client does. I'm compiling it with: bjam gcc boost=source target-os=linux openssl=pe variant=release geoip=off logging=none java_client
- java_client.cpp 56.28KB
Comment #2
Posted on Feb 9, 2011 by Helpful ElephantThe idea I have to solve this is to save the resume data whenever you receive it, and use that to start up with the next session.
You can do this by constructing libtorrent::create_torrent passing in the torrent_info object from the torrent, once you receive the metadata from the swarm. Then you can call create_torrent::generate() to get a bencoded dictionary of the torrent.
Comment #3
Posted on Feb 9, 2011 by Helpful ElephantComment deleted
Comment #4
Posted on Feb 9, 2011 by Helpful Elephant(No comment was entered for this change.)
Comment #5
Posted on Feb 9, 2011 by Swift MonkeyYes, I have already implemented that to work around this, by saving a .torrent file in the save_path as well:
torrent_info ti = h.get_torrent_info();
create_torrent new_torrent(ti);
std::vector<char> out;
bencode(std::back_inserter(out), new_torrent.generate());
save_file((h.save_path() / h.name()).string() + ".torrent", out);
Which works fine I guess, I just think it would be nicer to have everything in the .resume file, instead of keeping track of two files.
After looking into it more since my first post, if the 'info' dictionary from the torrent_info was stored in the .resume file, it wouldn't need any more metadata right? (the part I'm talking about looks like this, from dump_torrent: ''info': { 'length': 726827008, 'name': 'ubuntu-10.10-desktop-i386.iso', 'piece length': 524288, 'pieces': '2f7c363c74a83fcde79ff4.....' Since it looks like it stores everything else, like trackers, url_seeds and such.
Comment #6
Posted on Feb 10, 2011 by Helpful Elephantwell, for non-magnet links, you would probably want to store the tracker url as well, which is outside the info-dict. I'm definitely not opposed to storing the metadata in the resume file. I'm not sure what the best way would be though. Maybe there should be an option to the save_resume_data call, to also save the metadata. There would need to be some nice way of adding a torrent by only providing the resume file as well though.
Comment #7
Posted on Feb 10, 2011 by Swift MonkeyWhat do you prefer submitted patches to be against? The latest stable? (currently 0.15.5), or the latest revision in version control? I'll probably end up submitting a few, so what's best for you?
Comment #8
Posted on Feb 11, 2011 by Helpful Elephanttrunk. I'm trying to keep 0.15.x binary compatible as much as possible
Comment #9
Posted on Feb 14, 2011 by Swift MonkeyOk, I finally got around to doing this, here is the patch to save the info-dict in the .resume file (against latest SVN):
diff --git a/trunk/src/torrent.cpp b/trunk/src/torrent.cpp index c91dae7..f5e0245 100644 --- a/trunk/src/torrent.cpp +++ b/trunk/src/torrent.cpp @@ -4355,6 +4355,8 @@ namespace libtorrent const sha1_hash& info_hash = torrent_file().info_hash(); ret["info-hash"] = std::string((char*)info_hash.begin(), (char*)info_hash.end());
- 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();
I was about to add in a nice way of adding a torrent by only providing the resume file, but then I realized that a .resume file with the addition of an info-dict is actually a valid .torrent file (just with some extra fields, which are ignored anyway), no 'announce' field is needed, because the code that parses the .resume file uses trackers from it. So some basic code to start a torrent with just a resume file would be as follows:
error_code ec;
add_torrent_params p;
p.share_mode = share_mode;
p.save_path = save_path;
p.storage_mode = (storage_mode_t)allocation_mode;
p.paused = true;
p.duplicate_is_error = false;
p.auto_managed = true;
std::string filename = "/path/to/your.resume";
boost::intrusive_ptr<torrent_info> t;
std::vector<char> buf;
if (load_file(filename.c_str(), buf, ec) == 0){
printf(".resume data found: %s\n", filename.c_str());
// save the resume data
p.resume_data = &buf;
// load the torrent_info from it
t = new torrent_info(buf, buf.size(), ec);
}
torrent_handle h = ses.add_torrent(p, ec);
I'm not sure if there would be a good clean way of implementing this into the API, except storing the buffer information from the torrent_info call in the object, so it can be checked and set to resume_data later? Or just adding a 'free function' that does the above?
What are your thoughts on this?
Comment #10
Posted on Feb 14, 2011 by Swift MonkeySorry for two comments so close together.
Actually, maybe that last idea isn't so bad. If a std::vector* torrent_data; Was a member of torrent_info, and set when one of the constructors was called, when .add_torrent was called, it could simply check if params.ti.torrent_data was set, and, if so, point params.resume_data to it. This should only increase memory usage by the size of a pointer I think.
If you think that is a good enough solution, I'll implement it?
Comment #11
Posted on Feb 15, 2011 by Helpful Elephantthis is pretty neat. I think there should be an option to save the info dict in the resume file.
I'm not sure about the idea of having torrent_info potentially own the buffer it was parsed from. I like the first idea better, where this mechanism is wrapped in a free function (sort of like add_magnet_uri()).
Comment #12
Posted on Feb 15, 2011 by Swift MonkeyI actually just finished up implementing my idea from before, and I think it works out pretty cleanly. The only extra thing added was a std::vector to torrent_info, which stays empty unless we load from a resume file instead of a torrent file. Look at it and see what you think.
The only bad thing about a free function for this particular thing, is that it makes it hard to cleanly write one method to handle adding all types of torrents. It can be done, but it's like the first function I pasted up there, a hodgepodge of detecting and adding. I've attached an examples.cpp showing the two versions.
In the end, I guess it boils down to personal preference though. :)
- resume_without_torrent.patch 5.15KB
Comment #13
Posted on Feb 15, 2011 by Swift MonkeyAlright, I have implemented your request for an option to save info-dict in the resume file by adding another flag called 'torrent_handle::save_info_dict'. I'm not happy with the way I'm passing the flag along to write_resume_data though, but I'm not sure of another way without modifying calls all the way down to disk_io_thread. I need another pair of eyes on this I think.
Regardless, it works as-is, tell me what you think.
I'm attaching examples.cpp again with a slight fix, that's what I get for writing example code quickly I guess...
- examples.cpp 7.03KB
Comment #14
Posted on Feb 16, 2011 by Swift MonkeyHere is the same patch file as above, just updated to be against the latest SVN revision 5288.
Comment #15
Posted on Feb 20, 2011 by Helpful Elephantsorry for the delay. I will try to take a look at this today. and thanks for that patch!
It just occurred to me that maybe the default should be to always save the metadata for torrents that were added as a magnet link, and not for regular .torrent files
Comment #16
Posted on Feb 21, 2011 by Swift MonkeyTo do that you would have to have an extra field in torrent or torrent_handle to save that information, and you would probably want to always save the metadata for torrents started with anything but a .torrent file (including .resume files, magnet links, and info-hash@http links).
Is that what you were thinking? And, if so, would you want the option to save_info_dict left in, or removed?
Comment #17
Posted on Feb 22, 2011 by Helpful ElephantWhat do you think about this patch? it's different in that it doesn't load the resume file as a .torrent file, but it picks it up from the resume data still. Doing it this way seems a bit more symmetric. Obviously a client could still load resume data files as .torrent files still (but would have to pass the file as resume data as well).
- metadata_in_resume.diff 9.09KB
Comment #18
Posted on Feb 22, 2011 by Swift MonkeyThat looks like a good way to do it as well. I'm happy either way.
Comment #19
Posted on Feb 26, 2011 by Helpful ElephantI've just checked this in to trunk. please re-open if you encounter any errors. Thanks!
Status: Fixed
Labels:
Type-Enhancement
Priority-Medium