246 lines
7.4 KiB
C++
246 lines
7.4 KiB
C++
/* Copyright 2025, Michele "King_DuckZ" Santullo
|
|
* This file is part of ducktorrent.
|
|
*
|
|
* Ducktorrent is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Ducktorrent is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with ducktorrent. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "torrent_read.hpp"
|
|
#include "parser.hpp"
|
|
#include "visitors/debug_visitor.hpp"
|
|
#include "visitors/find_t_visitor.hpp"
|
|
#include <utility>
|
|
#include <fstream>
|
|
#include <system_error>
|
|
#include <boost/variant/apply_visitor.hpp>
|
|
#include <ciso646>
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <ranges>
|
|
#include <numeric>
|
|
|
|
namespace duck {
|
|
namespace {
|
|
std::string load_file (const std::filesystem::path& path) {
|
|
std::ifstream ifs(path);
|
|
if (ifs.is_open()) {
|
|
ifs >> std::noskipws;
|
|
|
|
return std::string(
|
|
std::istreambuf_iterator<char>(ifs),
|
|
std::istreambuf_iterator<char>()
|
|
);
|
|
}
|
|
else {
|
|
throw std::filesystem::filesystem_error(
|
|
"Error reading " + path.string(),
|
|
std::make_error_code(std::errc::no_such_file_or_directory)
|
|
);
|
|
}
|
|
}
|
|
|
|
template <std::size_t OutS, std::size_t PrefS, std::size_t PostS>
|
|
std::string_view build_fast_search_path (
|
|
const char (&prefix)[PrefS],
|
|
const char (&postfix)[PostS],
|
|
std::size_t index,
|
|
char (&out_buff)[OutS],
|
|
std::string& emergency_buff
|
|
) {
|
|
static_assert(PrefS >= 1 and PostS >= 1, "Expected null-terminated inputs");
|
|
|
|
constexpr std::size_t prefix_size = PrefS - 1;
|
|
constexpr std::size_t postfix_size = PostS - 1;
|
|
constexpr std::size_t output_max_size = OutS;
|
|
|
|
std::string_view retval;
|
|
auto num = std::to_string(index); //short string optimisation should prevent allocation
|
|
const std::size_t real_out_size = prefix_size + num.size() + postfix_size;
|
|
|
|
if (real_out_size <= output_max_size) {
|
|
//Avoid allocations, just because
|
|
std::copy_n(prefix, prefix_size, out_buff);
|
|
std::copy_n(num.data(), num.size(), out_buff + prefix_size);
|
|
std::copy_n(postfix, postfix_size, out_buff + prefix_size + num.size());
|
|
retval = std::string_view(out_buff, prefix_size + num.size() + postfix_size);
|
|
}
|
|
else {
|
|
//too long, I yield, allocate away!!
|
|
emergency_buff.reserve(real_out_size);
|
|
std::string_view prefix_sv{prefix, prefix_size};
|
|
emergency_buff += prefix_sv;
|
|
emergency_buff += num;
|
|
std::string_view postfix_sv{postfix, postfix_size};
|
|
emergency_buff += postfix_sv;
|
|
retval = emergency_buff;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
} //unnamed namespace
|
|
|
|
TorrentRead::TorrentRead (std::string_view torrent_path, std::filesystem::path workdir) :
|
|
m_workdir(std::move(workdir)),
|
|
m_torrent_path(torrent_path),
|
|
m_raw_torrent(load_file(m_torrent_path)),
|
|
m_parsed_values(parse_torrent(m_raw_torrent)),
|
|
m_cached_info_files(nullptr)
|
|
{
|
|
}
|
|
|
|
TorrentRead::~TorrentRead() noexcept = default;
|
|
|
|
void TorrentRead::print (std::ostream& out) const {
|
|
DebugVisitor visitor(out);
|
|
for (const TorrentValue& value : m_parsed_values) {
|
|
boost::apply_visitor(visitor, value);
|
|
}
|
|
}
|
|
|
|
std::size_t TorrentRead::raw_data_size() const {
|
|
return m_raw_torrent.size();
|
|
}
|
|
|
|
const std::vector<TorrentValue>& TorrentRead::parsed_values() const {
|
|
return m_parsed_values;
|
|
}
|
|
|
|
std::string_view TorrentRead::read_name() const {
|
|
return find_string("/info/name", m_parsed_values);
|
|
}
|
|
|
|
std::size_t TorrentRead::read_piece_length() const {
|
|
return find_int<std::size_t>("/info/piece length", m_parsed_values);
|
|
}
|
|
|
|
std::int_fast32_t TorrentRead::read_file_count() const {
|
|
return find_int<std::int_fast32_t>("/[[size]]", cached_info_files());
|
|
}
|
|
|
|
std::vector<std::string_view> TorrentRead::read_file_path(std::size_t index) const {
|
|
const TorrentValue* path_variant;
|
|
std::string buff2;
|
|
|
|
{
|
|
constexpr char prefix[] = "/[[";
|
|
constexpr char postfix[] = "]]/path";
|
|
constexpr std::size_t prefix_size = sizeof(prefix) / sizeof(prefix[0]) - 1u;
|
|
constexpr std::size_t postfix_size = sizeof(postfix) / sizeof(postfix[0]) - 1u;
|
|
|
|
//see comment in read_file_size()
|
|
//in this case a search should look like "/[[1234]]/path"
|
|
char buff[4 + prefix_size + postfix_size];
|
|
|
|
auto found = find_variant(
|
|
build_fast_search_path(prefix, postfix, index, buff, buff2),
|
|
cached_info_files()
|
|
);
|
|
if (not found)
|
|
return {};
|
|
|
|
path_variant = &found->get();
|
|
}
|
|
|
|
const auto path_size = find_int<std::int_fast32_t>("/[[size]]", *path_variant);
|
|
std::vector<std::string_view> retval;
|
|
retval.reserve(path_size);
|
|
|
|
{
|
|
constexpr char prefix[] = "/[[";
|
|
constexpr char postfix[] = "]]";
|
|
constexpr std::size_t prefix_size = sizeof(prefix) / sizeof(prefix[0]) - 1u;
|
|
constexpr std::size_t postfix_size = sizeof(postfix) / sizeof(postfix[0]) - 1u;
|
|
char buff[4 + prefix_size + postfix_size];
|
|
for (std::int_fast32_t z = 0; z < path_size; ++z) {
|
|
std::string_view piece = find_string(
|
|
build_fast_search_path(prefix, postfix, z, buff, buff2),
|
|
*path_variant
|
|
);
|
|
retval.push_back(piece);
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
std::string TorrentRead::read_joint_file_path(std::size_t index, char sep) const {
|
|
auto pieces = this->read_file_path(index);
|
|
const std::size_t out_size = std::accumulate(pieces.cbegin(), pieces.cend(), 0u,
|
|
[](std::size_t a, const std::string_view& b) {return a+b.size();}
|
|
);
|
|
|
|
std::string retval;
|
|
retval.reserve(out_size + std::max<std::size_t>(pieces.size(), 1u) - 1u);
|
|
|
|
std::ranges::copy(pieces | std::views::join_with(sep), std::back_inserter(retval));
|
|
return retval;
|
|
}
|
|
|
|
std::size_t TorrentRead::read_file_size (std::size_t index) const {
|
|
constexpr char prefix[] = "/[[";
|
|
constexpr char postfix[] = "]]/length";
|
|
constexpr std::size_t prefix_size = sizeof(prefix) / sizeof(prefix[0]) - 1u;
|
|
constexpr std::size_t postfix_size = sizeof(postfix) / sizeof(postfix[0]) - 1u;
|
|
|
|
//accomodate paths with indices up to 9999 without nullchar
|
|
//ie: "/[[1234]]/length" like this, if this comment doesn't go out of sync
|
|
//with the code the size on the stack should be 16
|
|
char buff[4 + prefix_size + postfix_size];
|
|
std::string buff2;
|
|
|
|
return find_int<std::size_t>(
|
|
build_fast_search_path(prefix, postfix, index, buff, buff2),
|
|
cached_info_files()
|
|
);
|
|
}
|
|
|
|
bool TorrentRead::read_is_private() const {
|
|
const auto val = find_int<unsigned int>("/info/private", m_parsed_values);
|
|
return (val ? true : false);
|
|
}
|
|
|
|
std::string_view TorrentRead::read_comment() const {
|
|
return find_string("/comment", m_parsed_values);
|
|
}
|
|
|
|
std::string_view TorrentRead::read_created_by() const {
|
|
return find_string("/created by", m_parsed_values);
|
|
}
|
|
|
|
std::string_view TorrentRead::read_announce() const {
|
|
return find_string("/announce", m_parsed_values);
|
|
}
|
|
|
|
std::time_t TorrentRead::read_creation_date() const {
|
|
return find_int<std::time_t>("/creation date", m_parsed_values);
|
|
}
|
|
|
|
const TorrentValue& TorrentRead::cached_info_files() const {
|
|
constexpr char info_files[] = "/info/files";
|
|
if (not m_cached_info_files) {
|
|
auto found = find_variant(info_files, m_parsed_values);
|
|
if (found) {
|
|
m_cached_info_files = &found->get();
|
|
}
|
|
}
|
|
|
|
if (not m_cached_info_files) {
|
|
throw std::runtime_error(
|
|
std::string{"Node \""} + info_files + "\" not found in " + m_torrent_path.string()
|
|
);
|
|
}
|
|
|
|
assert(m_cached_info_files);
|
|
return *m_cached_info_files;
|
|
}
|
|
|
|
} //namespace duck
|