optimized memory usage of torrent_info and file_storage

This commit is contained in:
Arvid Norberg 2010-11-24 23:49:22 +00:00
parent 7cd628e78d
commit 894db973e8
22 changed files with 501 additions and 204 deletions

View File

@ -1,3 +1,5 @@
* optimized memory usage of torrent_info and file_storage, forcing some API changes
around file_storage and file_entry
* support trackerid tracker extension
* graceful peer disconnect mode which finishes transactions before disconnecting peers
* support chunked encoding for web seeds

View File

@ -48,12 +48,12 @@ namespace
ct.add_node(std::make_pair(addr, port));
}
void add_file(file_storage& ct, file_entry const& fe
, std::string const& hash, std::string const& linkpath)
{
ct.add_file(fe, hash.empty() ? 0 : &sha1_hash(hash)
, linkpath.empty() ? 0 : &linkpath);
}
void add_file(file_storage& ct, file_entry const& fe
, std::string const& hash, std::string const& linkpath)
{
ct.add_file(fe, hash.empty() ? 0 : hash.c_str()
, linkpath.empty() ? 0 : &linkpath);
}
}
void bind_create_torrent()
@ -82,6 +82,12 @@ void bind_create_torrent()
#endif
.def("num_files", &file_storage::num_files)
.def("at", &file_storage::at, return_internal_reference<>())
.def("hash", &file_storage::hash)
.def("symlink", &file_storage::symlink, return_internal_reference<>())
.def("file_index", &file_storage::file_index)
.def("file_base", &file_storage::file_base)
.def("set_file_base", &file_storage::set_file_base)
.def("file_path", &file_storage::file_path)
.def("total_size", &file_storage::total_size)
.def("set_num_pieces", &file_storage::set_num_pieces)
.def("num_pieces", &file_storage::num_pieces)

View File

@ -102,6 +102,20 @@ namespace
bool get_send_stats(announce_entry const& ae)
{ return ae.send_stats; }
bool get_size(file_entry const& fe)
{ return fe.size; }
bool get_offset(file_entry const& fe)
{ return fe.offset; }
bool get_pad_file(file_entry const& fe)
{ return fe.pad_file; }
bool get_executable_attribute(file_entry const& fe)
{ return fe.executable_attribute; }
bool get_hidden_attribute(file_entry const& fe)
{ return fe.hidden_attribute; }
bool get_symlink_attribute(file_entry const& fe)
{ return fe.symlink_attribute; }
} // namespace unnamed
void bind_torrent_info()
@ -168,14 +182,14 @@ void bind_torrent_info()
;
class_<file_entry>("file_entry")
.add_property("path"
, make_getter(
&file_entry::path, return_value_policy<copy_non_const_reference>()
)
)
.def_readonly("offset", &file_entry::offset)
.def_readonly("size", &file_entry::size)
.def_readonly("file_base", &file_entry::file_base)
.def("filename", &file_entry::filename)
.def("set_name", &file_entry::set_name)
.add_property("pad_file", &get_pad_file)
.add_property("executable_attribute", &get_executable_attribute)
.add_property("hidden_attribute", &get_hidden_attribute)
.add_property("symlink_attribute", &get_symlink_attribute)
.add_property("offset", &get_offset)
.add_property("size", &get_size)
;
class_<announce_entry>("announce_entry", init<std::string const&>())

View File

@ -172,8 +172,12 @@ file structure. Its synopsis::
int piece_length() const;
int piece_size(int index) const;
sha1_hash const& hash(int index) const;
std::string const& symlink(int index) const;
sha1_hash const& hash(file_entry const& fe) const;
std::string const& symlink(file_entry const& fe) const;
time_t mtime(file_entry const& fe) const;
int file_index(file_entry const& fe) const;
size_type file_base(file_entry const& fe) const;
void set_file_base(file_entry const& fe, size_type off);
void set_name(std::string const& n);
void set_name(std::wstring const& n);
@ -218,17 +222,47 @@ can be changed by calling ``set_name``.
The built in functions to traverse a directory to add files will
make sure this requirement is fulfilled.
hash() symlink()
----------------
hash() symlink() mtime() file_index()
-------------------------------------
::
sha1_hash const& hash(int index) const;
std::string const& symlink(int index) const;
sha1_hash hash(file_entry const& fe) const;
std::string const& symlink(file_entry const& fe) const;
time_t mtime(file_entry const& fe) const;
int file_index(file_entry const& fe) const;
These functions are used to resolve the symlink index and file hash
index in ``file_entry``.
These functions are used to query the symlink, file hash,
modification time and the file-index from a ``file_entry``.
For these functions to function, the file entry must be an
actual object from this same ``file_storage`` object. It may
not be a copy.
The file hash is a sha-1 hash of the file, or 0 if none was
provided in the torrent file. This can potentially be used to
join a bittorrent network with other file sharing networks.
The modification time is the posix time when a file was last
modified when the torrent was created, or 0 if it was not provided.
The file index of a file is simply a 0 based index of the
file as they are ordered in the torrent file.
file_base() set_file_base()
---------------------------
::
size_type file_base(file_entry const& fe) const;
void set_file_base(file_entry const& fe, size_type off);
The file base of a file is the offset within the file on the filsystem
where it starts to write. For the most part, this is always 0. It's
possible to map several files (in the torrent) into a single file on
the filesystem by making them all point to the same filename, but with
different file bases, so that they don't overlap.
``torrent_info::remap_files`` can be used to use a new file layout.
create_torrent
==============

View File

@ -1350,9 +1350,6 @@ The ``torrent_info`` has the following synopsis::
{
public:
// flags for torrent_info constructor
enum flags_t { omit_filehashes = 1 };
// these constructors throws exceptions on error
torrent_info(sha1_hash const& info_hash, int flags = 0);
torrent_info(lazy_entry const& torrent_file, int flags = 0);
@ -1461,10 +1458,7 @@ torrent_info object. The overloads that do not take the extra error_code_ parame
always throw if an error occurs. These overloads are not available when building without
exception support.
The ``flags`` argument can be used to disable loading of potentially unnecessary hashes
for individual files (if included in the torrent file). This is especially useful if
you're loading torrents with thousands of files on a memory constrained system. If so,
pass in ``torrent_info::omit_filehashes`` as the flags argument.
The ``flags`` argument is currently unused.
add_tracker()
@ -1547,7 +1541,7 @@ iterators with the type ``file_entry``.
struct file_entry
{
std::string path;
std::string filename();
size_type offset;
size_type size;
size_type file_base;
@ -1560,9 +1554,9 @@ iterators with the type ``file_entry``.
bool symlink_attribute:1;
};
The ``path`` is the full (relative) path of each file. i.e. if it is a multi-file
torrent, all the files starts with a directory with the same name as ``torrent_info::name()``.
The filenames are encoded with UTF-8.
The ``filename`` function returns the filename of this file. It does not include the
path, just the leaf name. To get the full path name, use ``file_storage::file_path()``.
The filenames are unicode strings encoded in UTF-8.
``size`` is the size of the file (in bytes) and ``offset`` is the byte offset
of the file within the torrent. i.e. the sum of all the sizes of the files
@ -1584,7 +1578,7 @@ is set. To resolve the symlink, call ``file_storage::symlink(e.symlink_index)``.
``filehash_index`` is an index into an array of sha-1 hashes in ``file_storage``, or
-1 if this file doesn't have a hash specified. The hash is the hash of the actual
content of the file, and can be used to potentially find alternative sources for it.
To resolve the hash, use ``file_storage::hash(e.filehash_index)``.
To resolve the hash, use ``file_storage::hash(e)``.
``pad_file`` is set to true for files that are not part of the data of the torrent.
They are just there to make sure the next file is aligned to a particular byte offset

View File

@ -1715,7 +1715,7 @@ int main(int argc, char* argv[])
, pad_file?esc("34"):""
, progress / 10.f
, add_suffix(file_progress[i]).c_str()
, filename(info.file_at(i).path).c_str()
, filename(info.files().file_path(info.file_at(i))).c_str()
, pad_file?esc("0"):"");
out += str;
}

View File

@ -40,6 +40,8 @@ int main(int argc, char* argv[])
{
using namespace libtorrent;
printf("sizeof(file_entry): %d\n", int(sizeof(file_entry)));
if (argc != 2)
{
fputs("usage: dump_torrent torrent-file\n", stderr);
@ -70,7 +72,7 @@ int main(int argc, char* argv[])
return 1;
}
printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str());
// printf("\n\n----- raw info -----\n\n%s\n", print_entry(e).c_str());
torrent_info t(e, ec);
if (ec)
@ -78,6 +80,8 @@ int main(int argc, char* argv[])
fprintf(stderr, "%s\n", ec.message().c_str());
return 1;
}
e.clear();
std::vector<char>().swap(buf);
// print info about torrent
printf("\n\n----- torrent file info -----\n\n"
@ -106,6 +110,7 @@ int main(int argc, char* argv[])
"created by: %s\n"
"magnet link: %s\n"
"name: %s\n"
"number of files: %d\n"
"files:\n"
, t.num_pieces()
, t.piece_length()
@ -113,24 +118,26 @@ int main(int argc, char* argv[])
, t.comment().c_str()
, t.creator().c_str()
, make_magnet_uri(t).c_str()
, t.name().c_str());
, t.name().c_str()
, t.num_files());
int index = 0;
for (torrent_info::file_iterator i = t.begin_files();
i != t.end_files(); ++i, ++index)
{
int first = t.map_file(index, 0, 0).piece;
int last = t.map_file(index, (std::max)(i->size-1, size_type(0)), 0).piece;
printf(" %11"PRId64" %c%c%c%c [ %4d, %4d ] %s %s %s%s\n"
printf(" %11"PRId64" %c%c%c%c [ %4d, %4d ] %7u %s %s %s%s\n"
, i->size
, (i->pad_file?'p':'-')
, (i->executable_attribute?'x':'-')
, (i->hidden_attribute?'h':'-')
, (i->symlink_attribute?'l':'-')
, first, last
, i->filehash_index != -1 ? to_hex(t.files().hash(i->filehash_index).to_string()).c_str() : ""
, i->path.c_str()
, boost::uint32_t(t.files().mtime(*i))
, t.files().hash(*i) != sha1_hash(0) ? to_hex(t.files().hash(*i).to_string()).c_str() : ""
, t.files().file_path(*i).c_str()
, i->symlink_attribute ? "-> ": ""
, i->symlink_attribute && i->symlink_index != -1 ? t.files().symlink(i->symlink_index).c_str() : "");
, i->symlink_attribute && i->symlink_index != -1 ? t.files().symlink(*i).c_str() : "");
}
return 0;

View File

@ -84,7 +84,7 @@ int main(int argc, char const* argv[])
{
if (ti->file_at(i).size == files[i].first) continue;
fprintf(stderr, "Files for this torrent are missing or incomplete: %s was %"PRId64" bytes, expected %"PRId64" bytes\n"
, ti->file_at(i).path.c_str(), files[i].first, ti->file_at(i).size);
, ti->files().file_path(ti->file_at(i)).c_str(), files[i].first, ti->file_at(i).size);
return 1;
}

View File

@ -51,15 +51,16 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent
{
struct file_entry;
struct file_storage;
struct TORRENT_EXPORT file_pool : boost::noncopyable
{
file_pool(int size = 40): m_size(size), m_low_prio_io(true) {}
boost::intrusive_ptr<file> open_file(void* st, std::string const& p
, file_entry const& fe, int m, error_code& ec);
, file_entry const& fe, file_storage const& fs, int m, error_code& ec);
void release(void* st);
void release(void* st, file_entry const& fe);
void release(void* st, int file_index);
void resize(int size);
int size_limit() const { return m_size; }
void set_low_prio_io(bool b) { m_low_prio_io = b; }

View File

@ -41,7 +41,6 @@ POSSIBILITY OF SUCH DAMAGE.
#include "libtorrent/assert.hpp"
#include "libtorrent/peer_request.hpp"
#include "libtorrent/peer_id.hpp"
#include "libtorrent/copy_ptr.hpp"
namespace libtorrent
{
@ -49,43 +48,59 @@ namespace libtorrent
struct TORRENT_EXPORT file_entry
{
friend class file_storage;
file_entry()
: offset(0)
, size(0)
, file_base(0)
, mtime(0)
, file_index(0)
, filehash_index(-1)
: name(0)
, offset(0)
, symlink_index(-1)
, size(0)
, name_len(0)
, pad_file(false)
, hidden_attribute(false)
, executable_attribute(false)
, symlink_attribute(false)
, path_index(-1)
{}
std::string path;
file_entry(file_entry const& fe);
file_entry& operator=(file_entry const& fe);
~file_entry();
void set_name(char const* n, int borrow_chars = 0);
std::string filename() const;
private:
// This string is not necessarily null terminated!
// that's why it's private, to keep people away from it
char const* name;
public:
// the offset of this file inside the torrent
size_type offset;
// the size of this file
size_type size;
// the offset in the file where the storage starts.
// This is always 0 unless parts of the torrent is
// compressed into a single file, such as a so-called part file.
size_type file_base;
// modification time
time_t mtime;
// the index of this file, as ordered in the torrent
int file_index;
// index into file_storage::m_file_hashes or -1
// if this file does not have a hash
int filehash_index;
size_type offset:48;
// index into file_storage::m_symlinks or -1
// if this is not a symlink
int symlink_index;
size_type symlink_index:16;
// the size of this file
size_type size:48;
// the number of characters in the name. If this is
// 0, name is null terminated and owned by this object
// (i.e. it should be freed in the destructor). If
// the len is > 0, the name pointer doesn not belong
// to this object, and it's not null terminated
size_type name_len:10;
bool pad_file:1;
bool hidden_attribute:1;
bool executable_attribute:1;
bool symlink_attribute:1;
// the index into file_storage::m_paths. To get
// the full path to this file, concatenate the path
// from that array with the 'name' field in
// this struct
int path_index;
};
struct TORRENT_EXPORT file_slice
@ -114,11 +129,12 @@ namespace libtorrent
void reserve(int num_files);
void add_file(file_entry const& e, sha1_hash const* filehash = 0
, std::string const* symlink = 0);
void add_file(file_entry const& e, char const* filehash = 0
, std::string const* symlink = 0, time_t mtime = 0);
void add_file(std::string const& p, size_type size, int flags = 0
, std::time_t mtime = 0, std::string const& s_p = "");
void rename_file(int index, std::string const& new_filename);
#if TORRENT_USE_WSTRING
@ -149,17 +165,14 @@ namespace libtorrent
return m_files[index];
}
sha1_hash const& hash(int index) const
{
TORRENT_ASSERT(index >= 0 && index < int(m_file_hashes.size()));
return m_file_hashes[index];
}
std::string const& symlink(int index) const
{
TORRENT_ASSERT(index >= 0 && index < int(m_symlinks.size()));
return m_symlinks[index];
}
sha1_hash hash(file_entry const& fe) const;
std::string const& symlink(file_entry const& fe) const;
time_t mtime(file_entry const& fe) const;
int file_index(file_entry const& fe) const;
size_type file_base(file_entry const& fe) const;
void set_file_base(file_entry const& fe, size_type off);
std::string file_path(file_entry const& fe) const;
size_type total_size() const { return m_total_size; }
void set_num_pieces(int n) { m_num_pieces = n; }
@ -188,19 +201,40 @@ namespace libtorrent
private:
void update_path_index(file_entry& e);
void reorder_file(int index, int dst);
// the list of files that this torrent consists of
std::vector<file_entry> m_files;
// if there are sha1 hashes for each individual file
// each file_entry has an index into this vector
// and the actual hashes are in here
std::vector<sha1_hash> m_file_hashes;
// there are as many entries in this array as the
// m_files array. Each entry in m_files has a corresponding
// hash pointer in this array. The reason to split it up
// in separate arrays is to save memory in case the torrent
// doesn't have file hashes
std::vector<char const*> m_file_hashes;
// for files that are symlinks, the symlink
// path_index in the file_entry indexes
// this vector of strings
std::vector<std::string> m_symlinks;
// the modification times of each file. This vector
// is empty if no file have a modification time.
// each element corresponds to the file with the same
// index in m_files
std::vector<time_t> m_mtime;
// if any file has a non-zero file base (i.e. multiple
// files residing in the same physical file at different
// offsets)
std::vector<size_type> m_file_base;
// all unique paths files have. The file_entry::path_index
// points into this array
std::vector<std::string> m_paths;
// name of torrent. For multi-file torrents
// this is always the root directory
std::string m_name;

View File

@ -78,7 +78,7 @@ namespace libtorrent
none_t, dict_t, list_t, string_t, int_t
};
lazy_entry() : m_begin(0), m_end(0), m_type(none_t)
lazy_entry() : m_begin(0), m_len(0), m_type(none_t)
{ m_data.start = 0; }
entry_type_t type() const { return (entry_type_t)m_type; }
@ -92,7 +92,7 @@ namespace libtorrent
m_data.start = start;
m_size = length;
m_begin = start - 1; // include 'i'
m_end = start + length + 1; // include 'e'
m_len = length + 2; // include 'e'
}
size_type int_value() const;
@ -202,7 +202,7 @@ namespace libtorrent
void set_end(char const* end)
{
TORRENT_ASSERT(end > m_begin);
m_end = end;
m_len = end - m_begin;
}
void clear();
@ -235,7 +235,7 @@ namespace libtorrent
swap(m_data.start, e.m_data.start);
swap(m_size, e.m_size);
swap(m_begin, e.m_begin);
swap(m_end, e.m_end);
swap(m_len, e.m_len);
}
private:
@ -250,11 +250,16 @@ namespace libtorrent
// used for dictionaries and lists to record the range
// in the original buffer they are based on
char const* m_begin;
char const* m_end;
// the number of bytes this entry extends in the
// bencoded byffer
boost::uint32_t m_len;
boost::uint32_t m_size; // if list or dictionary, the number of items
boost::uint32_t m_capacity:29; // if list or dictionary, allocated number of items
unsigned int m_type:3;
// if list or dictionary, the number of items
boost::uint32_t m_size;
// if list or dictionary, allocated number of items
boost::uint32_t m_capacity:29;
// element type (dict, list, int, string)
boost::uint32_t m_type:3;
// non-copyable
lazy_entry(lazy_entry const&);

View File

@ -227,8 +227,6 @@ namespace libtorrent
{
public:
enum flags_t { omit_filehashes = 1 };
#ifndef BOOST_NO_EXCEPTIONS
torrent_info(lazy_entry const& torrent_file, int flags = 0);
torrent_info(char const* buffer, int size, int flags = 0);

View File

@ -118,7 +118,7 @@ namespace libtorrent
// return instead of crash in release mode
if (fs.num_files() == 0) return;
if (!m_multifile && has_parent_path(m_files.at(0).path)) m_multifile = true;
if (!m_multifile && has_parent_path(m_files.file_path(m_files.at(0)))) m_multifile = true;
// a piece_size of 0 means automatic
if (piece_size == 0 && !m_merkle_torrent)
@ -301,7 +301,7 @@ namespace libtorrent
if (!m_multifile)
{
if (m_include_mtime) info["mtime"] = m_files.at(0).mtime;
if (m_include_mtime) info["mtime"] = m_files.mtime(m_files.at(0));
info["length"] = m_files.at(0).size;
if (m_files.at(0).pad_file
|| m_files.at(0).hidden_attribute
@ -320,7 +320,7 @@ namespace libtorrent
{
entry& sympath_e = info["symlink path"];
std::string split = split_path(m_files.symlink(m_files.at(0).symlink_index));
std::string split = split_path(m_files.symlink(m_files.at(0)));
for (char const* e = split.c_str(); e != 0; e = next_path_element(e))
sympath_e.list().push_back(entry(e));
}
@ -340,13 +340,13 @@ namespace libtorrent
{
files.list().push_back(entry());
entry& file_e = files.list().back();
if (m_include_mtime) file_e["mtime"] = i->mtime;
if (m_include_mtime && m_files.mtime(*i)) file_e["mtime"] = m_files.mtime(*i);
file_e["length"] = i->size;
entry& path_e = file_e["path"];
TORRENT_ASSERT(has_parent_path(i->path));
TORRENT_ASSERT(has_parent_path(m_files.file_path(*i)));
std::string split = split_path(i->path);
std::string split = split_path(m_files.file_path(*i));
TORRENT_ASSERT(split.c_str() == m_files.name());
for (char const* e = next_path_element(split.c_str());
@ -370,7 +370,7 @@ namespace libtorrent
{
entry& sympath_e = file_e["symlink path"];
std::string split = split_path(m_files.symlink(i->symlink_index));
std::string split = split_path(m_files.symlink(*i));
for (char const* e = split.c_str(); e != 0; e = next_path_element(e))
sympath_e.list().push_back(entry(e));
}

View File

@ -41,14 +41,14 @@ POSSIBILITY OF SUCH DAMAGE.
namespace libtorrent
{
boost::intrusive_ptr<file> file_pool::open_file(void* st, std::string const& p
, file_entry const& fe, int m, error_code& ec)
, file_entry const& fe, file_storage const& fs, int m, error_code& ec)
{
TORRENT_ASSERT(st != 0);
TORRENT_ASSERT(is_complete(p));
TORRENT_ASSERT((m & file::rw_mask) == file::read_only
|| (m & file::rw_mask) == file::read_write);
mutex::scoped_lock l(m_mutex);
file_set::iterator i = m_files.find(std::make_pair(st, fe.file_index));
file_set::iterator i = m_files.find(std::make_pair(st, fs.file_index(fe)));
if (i != m_files.end())
{
lru_file_entry& e = i->second;
@ -77,7 +77,7 @@ namespace libtorrent
// the new read/write privilages
TORRENT_ASSERT(e.file_ptr->refcount() == 1);
e.file_ptr->close();
std::string full_path = combine_path(p, fe.path);
std::string full_path = combine_path(p, fs.file_path(fe));
if (!e.file_ptr->open(full_path, m, ec))
{
m_files.erase(i);
@ -115,12 +115,12 @@ namespace libtorrent
ec = error_code(ENOMEM, get_posix_category());
return e.file_ptr;
}
std::string full_path = combine_path(p, fe.path);
std::string full_path = combine_path(p, fs.file_path(fe));
if (!e.file_ptr->open(full_path, m, ec))
return boost::intrusive_ptr<file>();
e.mode = m;
e.key = st;
m_files.insert(std::make_pair(std::make_pair(st, fe.file_index), e));
m_files.insert(std::make_pair(std::make_pair(st, fs.file_index(fe)), e));
TORRENT_ASSERT(e.file_ptr->is_open());
return e.file_ptr;
}
@ -134,10 +134,10 @@ namespace libtorrent
m_files.erase(i);
}
void file_pool::release(void* st, file_entry const& fe)
void file_pool::release(void* st, int file_index)
{
mutex::scoped_lock l(m_mutex);
file_set::iterator i = m_files.find(std::make_pair(st, fe.file_index));
file_set::iterator i = m_files.find(std::make_pair(st, file_index));
if (i != m_files.end()) m_files.erase(i);
}

View File

@ -67,6 +67,88 @@ namespace libtorrent
return piece_length();
}
void file_storage::update_path_index(file_entry& e)
{
std::string parent = parent_path(e.filename());
if (parent.empty())
{
e.path_index = -1;
}
else
{
// do we already have this path in the path list?
std::vector<std::string>::reverse_iterator p
= std::find(m_paths.rbegin(), m_paths.rend(), parent);
if (p == m_paths.rend())
{
// no, we don't. add it
e.path_index = m_paths.size();
m_paths.push_back(parent);
}
else
{
// yes we do. use it
e.path_index = p.base() - m_paths.begin() - 1;
}
e.set_name(filename(e.filename()).c_str());
}
}
file_entry::~file_entry() { if (name_len == 0) free((void*)name); }
file_entry::file_entry(file_entry const& fe)
: name(0)
, offset(fe.offset)
, symlink_index(fe.symlink_index)
, size(fe.size)
, name_len(fe.name_len)
, pad_file(fe.pad_file)
, hidden_attribute(fe.hidden_attribute)
, executable_attribute(fe.executable_attribute)
, symlink_attribute(fe.symlink_attribute)
, path_index(fe.path_index)
{
set_name(fe.name, fe.name_len);
}
file_entry& file_entry::operator=(file_entry const& fe)
{
offset = fe.offset;
size = fe.size;
path_index = fe.path_index;
symlink_index = fe.symlink_index;
pad_file = fe.pad_file;
hidden_attribute = fe.hidden_attribute;
executable_attribute = fe.executable_attribute;
symlink_attribute = fe.symlink_attribute;
set_name(fe.name, fe.name_len);
return *this;
}
void file_entry::set_name(char const* n, int borrow_chars)
{
TORRENT_ASSERT(borrow_chars >= 0);
if (borrow_chars > 1023) borrow_chars = 1023;
if (name_len == 0) free((void*)name);
if (n == 0)
{
TORRENT_ASSERT(borrow_chars == 0);
name = 0;
}
else
{
name = borrow_chars ? n : strdup(n);
}
name_len = borrow_chars;
}
std::string file_entry::filename() const
{
if (name_len) return std::string(name, name_len);
return name ? name : "";
}
#if TORRENT_USE_WSTRING
void file_storage::set_name(std::wstring const& n)
{
@ -80,7 +162,8 @@ namespace libtorrent
TORRENT_ASSERT(index >= 0 && index < int(m_files.size()));
std::string utf8;
wchar_utf8(new_filename, utf8);
m_files[index].path = utf8;
m_files[index].set_name(utf8.c_str());
update_path_index(m_files[index]);
}
void file_storage::add_file(std::wstring const& file, size_type size, int flags
@ -95,7 +178,8 @@ namespace libtorrent
void file_storage::rename_file(int index, std::string const& new_filename)
{
TORRENT_ASSERT(index >= 0 && index < int(m_files.size()));
m_files[index].path = new_filename;
m_files[index].set_name(new_filename.c_str());
update_path_index(m_files[index]);
}
namespace
@ -149,7 +233,7 @@ namespace libtorrent
{
file_slice f;
f.file_index = file_iter - begin();
f.offset = file_offset + file_iter->file_base;
f.offset = file_offset + file_base(*file_iter);
f.size = (std::min)(file_iter->size - file_offset, (size_type)size);
size -= f.size;
file_offset += f.size;
@ -161,6 +245,13 @@ namespace libtorrent
return ret;
}
std::string file_storage::file_path(file_entry const& fe) const
{
TORRENT_ASSERT(fe.path_index >= -1 && fe.path_index < int(m_paths.size()));
if (fe.path_index == -1) return fe.filename();
return combine_path(m_paths[fe.path_index], fe.filename());
}
peer_request file_storage::map_file(int file_index, size_type file_offset
, int size) const
{
@ -196,9 +287,8 @@ namespace libtorrent
TORRENT_ASSERT(m_name == split_path(file).c_str());
m_files.push_back(file_entry());
file_entry& e = m_files.back();
e.file_index = m_files.size() - 1;
e.set_name(file.c_str());
e.size = size;
e.path = file;
e.offset = m_total_size;
e.pad_file = (flags & pad_file) != 0;
e.hidden_attribute = (flags & attribute_hidden) != 0;
@ -208,45 +298,137 @@ namespace libtorrent
{
e.symlink_index = m_symlinks.size();
m_symlinks.push_back(symlink_path);
}
e.mtime = mtime;
if (mtime)
{
if (m_mtime.size() < m_files.size()) m_mtime.resize(m_files.size());
m_mtime[m_files.size() - 1] = mtime;
}
update_path_index(e);
m_total_size += size;
}
void file_storage::add_file(file_entry const& ent, sha1_hash const* filehash
, std::string const* symlink)
void file_storage::add_file(file_entry const& ent, char const* filehash
, std::string const* symlink, time_t mtime)
{
if (!has_parent_path(ent.path))
if (!has_parent_path(ent.filename()))
{
// you have already added at least one file with a
// path to the file (branch_path), which means that
// all the other files need to be in the same top
// directory as the first file.
TORRENT_ASSERT(m_files.empty());
m_name = ent.path;
m_name = ent.filename();
}
else
{
if (m_files.empty())
m_name = *ent.path.begin();
m_name = split_path(ent.filename()).c_str();
}
m_files.push_back(ent);
file_entry& e = m_files.back();
e.offset = m_total_size;
e.file_index = m_files.size() - 1;
m_total_size += ent.size;
if (filehash)
{
e.filehash_index = m_file_hashes.size();
m_file_hashes.push_back(*filehash);
if (m_file_hashes.size() < m_files.size()) m_file_hashes.resize(m_files.size());
m_file_hashes[m_files.size() - 1] = filehash;
}
if (symlink)
{
e.symlink_index = m_symlinks.size();
m_symlinks.push_back(*symlink);
}
if (mtime)
{
if (m_mtime.size() < m_files.size()) m_mtime.resize(m_files.size());
m_mtime[m_files.size() - 1] = mtime;
}
update_path_index(e);
}
sha1_hash file_storage::hash(file_entry const& fe) const
{
int index = &fe - &m_files[0];
if (index >= m_file_hashes.size()) return sha1_hash(0);
return sha1_hash(m_file_hashes[index]);
}
std::string const& file_storage::symlink(file_entry const& fe) const
{
TORRENT_ASSERT(fe.symlink_index < int(m_symlinks.size()));
return m_symlinks[fe.symlink_index];
}
time_t file_storage::mtime(file_entry const& fe) const
{
int index = &fe - &m_files[0];
if (index >= m_mtime.size()) return 0;
return m_mtime[index];
}
int file_storage::file_index(file_entry const& fe) const
{
int index = &fe - &m_files[0];
TORRENT_ASSERT(index >= 0 && index < m_files.size());
return index;
}
void file_storage::set_file_base(file_entry const& fe, size_type off)
{
int index = &fe - &m_files[0];
TORRENT_ASSERT(index >= 0 && index < m_files.size());
if (m_file_base.size() <= index) m_file_base.resize(index);
m_file_base[index] = off;
}
size_type file_storage::file_base(file_entry const& fe) const
{
int index = &fe - &m_files[0];
if (index >= m_file_base.size()) return 0;
return m_file_base[index];
}
bool compare_file_entry_size(file_entry const& fe1, file_entry const& fe2)
{ return fe1.size < fe2.size; }
void file_storage::reorder_file(int index, int dst)
{
file_entry e = m_files[index];
m_files.erase(m_files.begin() + index);
m_files.insert(m_files.begin() + dst, e);
if (!m_mtime.empty())
{
time_t mtime = 0;
if (m_mtime.size() > index)
{
mtime = m_mtime[index];
m_mtime.erase(m_mtime.begin() + index);
}
m_mtime.insert(m_mtime.begin() + dst, mtime);
}
if (!m_file_hashes.empty())
{
char const* fh = 0;
if (m_file_hashes.size() > index)
{
fh = m_file_hashes[index];
m_file_hashes.erase(m_file_hashes.begin() + index);
}
m_file_hashes.insert(m_file_hashes.begin() + dst, fh);
}
if (!m_file_base.empty())
{
size_type base = 0;
if (m_file_base.size() > index)
{
base = m_file_base[index];
m_file_base.erase(m_file_base.begin() + index);
}
m_file_base.insert(m_file_base.begin() + dst, base);
}
}
void file_storage::optimize(int pad_file_limit)
@ -263,10 +445,10 @@ namespace libtorrent
// put the largest file at the front, to make sure
// it's aligned
std::vector<file_entry>::iterator i = std::max_element(m_files.begin(), m_files.end()
, boost::bind(&file_entry::size, _1) < boost::bind(&file_entry::size, _2));
, &compare_file_entry_size);
using std::iter_swap;
iter_swap(i, m_files.begin());
int index = file_index(*i);
reorder_file(index, 0);
size_type off = 0;
int padding_file = 0;
@ -296,9 +478,9 @@ namespace libtorrent
if (best_match != m_files.end())
{
// we found one
file_entry e = *best_match;
m_files.erase(best_match);
i = m_files.insert(i, e);
int index = file_index(*best_match);
reorder_file(index, file_index(*i));
i->offset = off;
off += i->size;
continue;
@ -311,10 +493,9 @@ namespace libtorrent
i = m_files.insert(i, e);
i->size = pad_size;
i->offset = off;
i->file_base = 0;
char name[10];
std::sprintf(name, "%d", padding_file);
i->path = combine_path("_____padding_file", name);
char name[30];
std::sprintf(name, ".____padding_file/%d", padding_file);
i->set_name(name);
i->pad_file = true;
off += pad_size;
++padding_file;

View File

@ -245,7 +245,7 @@ namespace libtorrent
m_data.start = start;
m_size = length;
m_begin = start - 1 - num_digits(length);
m_end = start + length;
m_len = start - m_begin + length;
}
namespace
@ -400,7 +400,7 @@ namespace libtorrent
std::pair<char const*, int> lazy_entry::data_section() const
{
typedef std::pair<char const*, int> return_t;
return return_t(m_begin, m_end - m_begin);
return return_t(m_begin, m_len);
}
#if TORRENT_USE_IOSTREAM

View File

@ -166,7 +166,7 @@ namespace libtorrent
{
file_status s;
error_code ec;
stat_file(combine_path(save_path, i->path), &s, ec);
stat_file(combine_path(save_path, storage.file_path(*i)), &s, ec);
if (!ec)
{
@ -216,7 +216,7 @@ namespace libtorrent
file_status s;
error_code ec;
stat_file(combine_path(p, i->path), &s, ec);
stat_file(combine_path(p, fs.file_path(*i)), &s, ec);
if (!ec)
{
@ -516,7 +516,7 @@ namespace libtorrent
for (file_storage::iterator file_iter = files().begin(),
end_iter = files().end(); file_iter != end_iter; ++file_iter)
{
std::string file_path = combine_path(m_save_path, file_iter->path);
std::string file_path = combine_path(m_save_path, files().file_path(*file_iter));
std::string dir = parent_path(file_path);
if (dir != last_path)
@ -527,7 +527,7 @@ namespace libtorrent
create_directories(last_path, ec);
}
int file_index = file_iter - files().begin();
int file_index = files().file_index(*file_iter);
// ignore files that have priority 0
if (int(m_file_priority.size()) > file_index
@ -592,7 +592,7 @@ namespace libtorrent
{
error_code ec;
file_status s;
stat_file(combine_path(m_save_path, i->path), &s, ec);
stat_file(combine_path(m_save_path, files().file_path(*i)), &s, ec);
if (ec) continue;
if (s.mode & file_status::regular_file && i->size > 0)
return true;
@ -603,8 +603,8 @@ namespace libtorrent
bool storage::rename_file(int index, std::string const& new_filename)
{
if (index < 0 || index >= m_files.num_files()) return true;
std::string old_name = combine_path(m_save_path, files().at(index).path);
m_pool.release(this, files().at(index));
std::string old_name = combine_path(m_save_path, files().file_path(files().at(index)));
m_pool.release(this, index);
error_code ec;
rename(old_name, combine_path(m_save_path, new_filename), ec);
@ -650,8 +650,9 @@ namespace libtorrent
for (file_storage::iterator i = files().begin()
, end(files().end()); i != end; ++i)
{
std::string p = combine_path(m_save_path, i->path);
std::string bp = parent_path(i->path);
std::string fp = files().file_path(*i);
std::string p = combine_path(m_save_path, fp);
std::string bp = parent_path(fp);
std::pair<iter_t, bool> ret;
ret.second = true;
while (ret.second && !bp.empty())
@ -861,7 +862,7 @@ namespace libtorrent
for (file_storage::iterator i = f.begin()
, end(f.end()); i != end; ++i)
{
std::string split = split_path(i->path);
std::string split = split_path(f.file_path(*i));
to_move.insert(to_move.begin(), split);
}
@ -1178,8 +1179,8 @@ ret:
TORRENT_ASSERT(int(slices.size()) > counter);
size_type slice_size = slices[counter].size;
TORRENT_ASSERT(slice_size == file_bytes_left);
TORRENT_ASSERT(files().at(slices[counter].file_index).path
== file_iter->path);
TORRENT_ASSERT(&files().at(slices[counter].file_index)
== &*file_iter);
++counter;
#endif
@ -1201,7 +1202,7 @@ ret:
file_handle = open_file(*file_iter, op.mode, ec);
if (!file_handle || ec)
{
std::string path = combine_path(m_save_path, file_iter->path);
std::string path = combine_path(m_save_path, files().file_path(*file_iter));
TORRENT_ASSERT(ec);
set_error(path, ec);
return -1;
@ -1215,23 +1216,24 @@ ret:
// read is unaligned, we need to fall back on a slow
// special read that reads aligned buffers and copies
// it into the one supplied
size_type adjusted_offset = files().file_base(*file_iter) + file_offset;
if ((file_handle->open_mode() & file::no_buffer)
&& (((file_iter->file_base + file_offset) & (file_handle->pos_alignment()-1)) != 0
&& ((adjusted_offset & (file_handle->pos_alignment()-1)) != 0
|| (uintptr_t(tmp_bufs->iov_base) & (file_handle->buf_alignment()-1)) != 0))
{
bytes_transferred = (this->*op.unaligned_op)(file_handle, file_iter->file_base
+ file_offset, tmp_bufs, num_tmp_bufs, ec);
bytes_transferred = (this->*op.unaligned_op)(file_handle, adjusted_offset
, tmp_bufs, num_tmp_bufs, ec);
}
else
{
bytes_transferred = (int)((*file_handle).*op.regular_op)(file_iter->file_base
+ file_offset, tmp_bufs, num_tmp_bufs, ec);
bytes_transferred = (int)((*file_handle).*op.regular_op)(adjusted_offset
, tmp_bufs, num_tmp_bufs, ec);
}
file_offset = 0;
if (ec)
{
set_error(combine_path(m_save_path, file_iter->path), ec);
set_error(combine_path(m_save_path, files().file_path(*file_iter)), ec);
return -1;
}
@ -1312,12 +1314,12 @@ ret:
int cache_setting = m_settings ? settings().disk_io_write_mode : 0;
if (cache_setting == session_settings::disable_os_cache
|| (cache_setting == session_settings::disable_os_cache_for_aligned_files
&& ((fe.offset + fe.file_base) & (m_page_size-1)) == 0))
&& ((fe.offset + files().file_base(fe)) & (m_page_size-1)) == 0))
mode |= file::no_buffer;
if (!m_allocate_files) mode |= file::sparse;
if (m_settings && settings().no_atime_storage) mode |= file::no_atime;
return m_pool.open_file(const_cast<storage*>(this), m_save_path, fe, mode, ec);
return m_pool.open_file(const_cast<storage*>(this), m_save_path, fe, files(), mode, ec);
}
storage_interface* default_storage_constructor(file_storage const& fs

View File

@ -4142,7 +4142,7 @@ namespace libtorrent
for (torrent_info::file_iterator i = m_torrent_file->begin_files()
, end(m_torrent_file->end_files()); i != end; ++i)
{
fl.push_back(i->path);
fl.push_back(m_torrent_file->files().file_path(*i));
}
}

View File

@ -184,8 +184,8 @@ namespace libtorrent
void verify_encoding(file_entry& target)
{
std::string p = target.path;
if (!verify_encoding(p, true)) target.path = p;
std::string p = target.filename();
if (!verify_encoding(p, true)) target.set_name(p.c_str());
}
// TODO: should this take a char const*?
@ -233,18 +233,17 @@ namespace libtorrent
}
bool extract_single_file(lazy_entry const& dict, file_entry& target
, std::string const& root_dir, sha1_hash* filehash, std::string* symlink)
, std::string const& root_dir, lazy_entry const** filehash, std::string* symlink
, lazy_entry const** filename, time_t* mtime)
{
if (dict.type() != lazy_entry::dict_t) return false;
lazy_entry const* length = dict.dict_find("length");
if (length == 0 || length->type() != lazy_entry::int_t)
return false;
target.size = length->int_value();
target.path = root_dir;
target.file_base = 0;
size_type ts = dict.dict_find_int_value("mtime", -1);
if (ts >= 0) target.mtime = std::time_t(ts);
if (ts > 0) *mtime = std::time_t(ts);
// prefer the name.utf-8
// because if it exists, it is more
@ -256,21 +255,25 @@ namespace libtorrent
if (p == 0 || p->type() != lazy_entry::list_t)
return false;
std::string path = root_dir;
for (int i = 0, end(p->list_size()); i < end; ++i)
{
if (p->list_at(i)->type() != lazy_entry::string_t)
return false;
std::string path_element = p->list_at(i)->string_value();
if (i == end - 1) *filename = p->list_at(i);
trim_path_element(path_element);
target.path = combine_path(target.path, path_element);
path = combine_path(path, path_element);
}
target.path = sanitize_path(target.path);
path = sanitize_path(path);
verify_encoding(target);
// bitcomet pad file
if (target.path.find("_____padding_file_") != std::string::npos)
if (path.find("_____padding_file_") != std::string::npos)
target.pad_file = true;
target.set_name(path.c_str());
lazy_entry const* attr = dict.dict_find_string("attr");
if (attr)
{
@ -288,11 +291,7 @@ namespace libtorrent
lazy_entry const* fh = dict.dict_find_string("sha1");
if (fh && fh->string_length() == 20 && filehash)
{
std::memcpy(&(*filehash)[0], fh->string_ptr(), 20);
// indicate that the file has a filehash
target.filehash_index = 0;
}
*filehash = fh;
lazy_entry const* s_p = dict.dict_find("symlink path");
if (s_p != 0 && s_p->type() == lazy_entry::list_t && symlink)
@ -332,16 +331,19 @@ namespace libtorrent
};
bool extract_files(lazy_entry const& list, file_storage& target
, std::string const& root_dir)
, std::string const& root_dir, ptrdiff_t info_ptr_diff)
{
if (list.type() != lazy_entry::list_t) return false;
target.reserve(list.list_size());
for (int i = 0, end(list.list_size()); i < end; ++i)
{
sha1_hash file_hash;
lazy_entry const* file_hash = 0;
time_t mtime = 0;
std::string symlink;
file_entry e;
if (!extract_single_file(*list.list_at(i), e, root_dir, &file_hash, &symlink))
lazy_entry const* fee = 0;
if (!extract_single_file(*list.list_at(i), e, root_dir
, &file_hash, &symlink, &fee, &mtime))
return false;
// TODO: this logic should be a separate step
@ -352,15 +354,31 @@ namespace libtorrent
// as long as this file already exists
// increase the counter
while (!files.insert(e.path).second)
std::string path = e.filename();
while (!files.insert(path).second)
{
++cnt;
char suffix[50];
snprintf(suffix, sizeof(suffix), ".%d%s", cnt, extension(e.path).c_str());
replace_extension(e.path, suffix);
snprintf(suffix, sizeof(suffix), ".%d%s", cnt, extension(path).c_str());
replace_extension(path, suffix);
}
e.set_name(path.c_str());
target.add_file(e, file_hash ? file_hash->string_ptr() + info_ptr_diff : 0
, e.symlink_index != -1 ? &symlink : 0, mtime);
// This is a memory optimization! Instead of having
// each entry keep a string for its filename, make it
// simply point into the info-section buffer
file_entry const& fe = target.at(target.num_files() - 1);
// TODO: once the filename renaming is removed from here
// this check can be removed as well
if (fee && fe.filename() == fee->string_value())
{
// this string pointer does not necessarily point into
// the m_info_section buffer.
char const* str_ptr = fee->string_ptr() + info_ptr_diff;
const_cast<file_entry&>(fe).set_name(str_ptr, fee->string_length());
}
target.add_file(e, e.filehash_index != -1 ? &file_hash : 0
, e.symlink_index != -1 ? &symlink : 0);
}
return true;
}
@ -748,6 +766,11 @@ namespace libtorrent
TORRENT_ASSERT(section.first[0] == 'd');
TORRENT_ASSERT(section.first[m_info_section_size-1] == 'e');
// when translating a pointer that points into the 'info' tree's
// backing buffer, into a pointer to our copy of the info section,
// this is the pointer offset to use.
ptrdiff_t info_ptr_diff = m_info_section.get() - section.first;
// extract piece length
int piece_length = info.dict_find_int_value("piece length", -1);
if (piece_length <= 0)
@ -784,12 +807,12 @@ namespace libtorrent
// if there's no list of files, there has to be a length
// field.
file_entry e;
e.path = name;
e.set_name(name.c_str());
e.offset = 0;
e.size = info.dict_find_int_value("length", -1);
size_type ts = info.dict_find_int_value("mtime", -1);
if (ts >= 0)
e.mtime = std::time_t(ts);
time_t mtime = 0;
if (ts > 0) mtime = std::time_t(ts);
lazy_entry const* attr = info.dict_find_string("attr");
if (attr)
{
@ -818,29 +841,23 @@ namespace libtorrent
e.symlink_index = 0;
}
lazy_entry const* fh = info.dict_find_string("sha1");
sha1_hash filehash;
if (fh && fh->string_length() == 20)
{
std::memcpy(&filehash[0], fh->string_ptr(), 20);
e.filehash_index = 0;
}
if (fh && fh->string_length() != 20) fh = 0;
// bitcomet pad file
if (e.path.find("_____padding_file_") != std::string::npos)
if (e.filename().find("_____padding_file_") != std::string::npos)
e.pad_file = true;
if (e.size < 0)
{
ec = errors::torrent_invalid_length;
return false;
}
bool omit_hash = (flags & torrent_info::omit_filehashes) || e.filehash_index == -1;
m_files.add_file(e, omit_hash ? 0 : &filehash
, e.symlink_index != -1 ? &symlink : 0);
m_files.add_file(e, fh ? fh->string_ptr() + info_ptr_diff : 0
, e.symlink_index != -1 ? &symlink : 0, mtime);
m_multifile = false;
}
else
{
if (!extract_files(*i, m_files, name))
if (!extract_files(*i, m_files, name, info_ptr_diff))
{
ec = errors::torrent_file_parse_failed;
return false;
@ -873,7 +890,7 @@ namespace libtorrent
return false;
}
m_piece_hashes = m_info_section.get() + (pieces->string_ptr() - section.first);
m_piece_hashes = pieces->string_ptr() + info_ptr_diff;
TORRENT_ASSERT(m_piece_hashes >= m_info_section.get());
TORRENT_ASSERT(m_piece_hashes < m_info_section.get() + m_info_section_size);
}
@ -1204,7 +1221,7 @@ namespace libtorrent
os << "piece length: " << piece_length() << "\n";
os << "files:\n";
for (file_storage::iterator i = m_files.begin(); i != m_files.end(); ++i)
os << " " << std::setw(11) << i->size << " " << i->path << "\n";
os << " " << std::setw(11) << i->size << " " << m_files.file_path(*i) << "\n";
}
// ------- end deprecation -------

View File

@ -210,7 +210,7 @@ namespace libtorrent
if (using_proxy)
{
request += m_url;
std::string path = info.orig_files().at(f.file_index).path;
std::string path = info.orig_files().file_path(info.orig_files().at(f.file_index));
#ifdef TORRENT_WINDOWS
convert_path_to_posix(path);
#endif
@ -219,7 +219,7 @@ namespace libtorrent
else
{
std::string path = m_path;
path += info.orig_files().at(f.file_index).path;
path += info.orig_files().file_path(info.orig_files().at(f.file_index));
#ifdef TORRENT_WINDOWS
convert_path_to_posix(path);
#endif
@ -421,7 +421,7 @@ namespace libtorrent
int file_index = m_file_requests.front();
torrent_info const& info = t->torrent_file();
std::string path = info.orig_files().at(file_index).path;
std::string path = info.orig_files().file_path(info.orig_files().at(file_index));
#ifdef TORRENT_WINDOWS
convert_path_to_posix(path);
#endif

View File

@ -804,7 +804,7 @@ void run_test(std::string const& test_path, bool unbuffered)
file_storage fs;
fs.add_file("temp_storage/test1.tmp", 3 * piece_size);
libtorrent::create_torrent t(fs, piece_size, -1, 0);
TEST_CHECK(fs.begin()->path == "temp_storage/test1.tmp");
TEST_CHECK(fs.file_path(*fs.begin()) == "temp_storage/test1.tmp");
t.set_hash(0, hasher(piece0, piece_size).final());
t.set_hash(1, hasher(piece1, piece_size).final());
t.set_hash(2, hasher(piece2, piece_size).final());

View File

@ -151,7 +151,8 @@ void test_transfer(boost::intrusive_ptr<torrent_info> torrent_file
if (proxy) stop_proxy(8002);
TEST_CHECK(exists(combine_path("./tmp2_web_seed", torrent_file->file_at(0).path)));
TEST_CHECK(exists(combine_path("./tmp2_web_seed", torrent_file->files().file_path(
torrent_file->file_at(0)))));
remove_all("./tmp2_web_seed", ec);
}
@ -256,10 +257,11 @@ int run_suite(char const* protocol, bool test_url_seed, bool chunked_encoding)
// verify that the file hashes are correct
for (int i = 0; i < torrent_file->num_files(); ++i)
{
TEST_CHECK(torrent_file->file_at(i).filehash_index >= 0);
sha1_hash h1 = torrent_file->files().hash(torrent_file->file_at(i).filehash_index);
sha1_hash h2 = file_hash(combine_path("./tmp1_web_seed", torrent_file->file_at(i).path));
fprintf(stderr, "%s: %s == %s\n", torrent_file->file_at(i).path.c_str()
sha1_hash h1 = torrent_file->files().hash(torrent_file->file_at(i));
sha1_hash h2 = file_hash(combine_path("./tmp1_web_seed"
, torrent_file->files().file_path(torrent_file->file_at(i))));
fprintf(stderr, "%s: %s == %s\n"
, torrent_file->files().file_path(torrent_file->file_at(i)).c_str()
, to_hex(h1.to_string()).c_str(), to_hex(h2.to_string()).c_str());
TEST_EQUAL(h1, h2);
}