ducktorrent/src/torrent_read.cpp
2025-04-07 01:27:59 +01:00

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