From 5a78ef04de4e187dfb792e3ce4ffdc7a62c4c670 Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Mon, 7 Apr 2025 13:22:47 +0100 Subject: [PATCH] Split project into cli and shared object Since it's a .so now I also added a C API that wraps TorrentRead --- meson.build | 2 +- src/{ => cli}/main.cpp | 2 +- src/cli/meson.build | 9 + src/ducktorrent/c_api_torrent_read.cpp | 213 ++++++++++++++++++ .../include/ducktorrent}/parser.hpp | 0 .../include/ducktorrent/torrent_read.h | 85 +++++++ .../include/ducktorrent}/torrent_read.hpp | 1 + src/ducktorrent/meson.build | 34 +++ src/{ => ducktorrent}/parser.cpp | 2 +- src/{ => ducktorrent}/split.cpp | 0 src/{ => ducktorrent}/split.hpp | 0 src/{ => ducktorrent}/torrent_read.cpp | 8 +- .../visitors/debug_visitor.cpp | 0 .../visitors/debug_visitor.hpp | 2 +- .../visitors/find_t_visitor.cpp | 0 .../visitors/find_t_visitor.hpp | 2 +- src/meson.build | 15 +- 17 files changed, 355 insertions(+), 20 deletions(-) rename src/{ => cli}/main.cpp (99%) create mode 100644 src/cli/meson.build create mode 100644 src/ducktorrent/c_api_torrent_read.cpp rename src/{ => ducktorrent/include/ducktorrent}/parser.hpp (100%) create mode 100644 src/ducktorrent/include/ducktorrent/torrent_read.h rename src/{ => ducktorrent/include/ducktorrent}/torrent_read.hpp (98%) create mode 100644 src/ducktorrent/meson.build rename src/{ => ducktorrent}/parser.cpp (99%) rename src/{ => ducktorrent}/split.cpp (100%) rename src/{ => ducktorrent}/split.hpp (100%) rename src/{ => ducktorrent}/torrent_read.cpp (97%) rename src/{ => ducktorrent}/visitors/debug_visitor.cpp (100%) rename src/{ => ducktorrent}/visitors/debug_visitor.hpp (97%) rename src/{ => ducktorrent}/visitors/find_t_visitor.cpp (100%) rename src/{ => ducktorrent}/visitors/find_t_visitor.hpp (99%) diff --git a/meson.build b/meson.build index b548d03..436211e 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('ducktorrent', 'cpp', - version: '0.2.0', + version: '0.2.1', meson_version: '>=0.63.0', default_options: [ 'buildtype=release', diff --git a/src/main.cpp b/src/cli/main.cpp similarity index 99% rename from src/main.cpp rename to src/cli/main.cpp index 95ea215..3c130db 100644 --- a/src/main.cpp +++ b/src/cli/main.cpp @@ -15,7 +15,7 @@ * along with ducktorrent. If not, see . */ -#include "torrent_read.hpp" +#include "ducktorrent/torrent_read.hpp" #include #include diff --git a/src/cli/meson.build b/src/cli/meson.build new file mode 100644 index 0000000..09a71a2 --- /dev/null +++ b/src/cli/meson.build @@ -0,0 +1,9 @@ +executable(meson.project_name(), + 'main.cpp', + dependencies: [ + boost_dep, + libstriezel_dep, + ducktorrent_dep, + ], + install: true, +) diff --git a/src/ducktorrent/c_api_torrent_read.cpp b/src/ducktorrent/c_api_torrent_read.cpp new file mode 100644 index 0000000..1eaaf2b --- /dev/null +++ b/src/ducktorrent/c_api_torrent_read.cpp @@ -0,0 +1,213 @@ +/* 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 . + */ + +#include "ducktorrent/torrent_read.h" +#include "ducktorrent/torrent_read.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" +struct dt_torrent { + dt_torrent (std::string_view path) : + reader(path, "") + { } + + duck::TorrentRead reader; + std::string str_buff; + std::vector vec_buff; + std::vector> announce_list_buff; +}; + +extern "C" +struct dt_torrent* dt_make_reader_from_path (dt_string path) { + try { + std::string_view path_sv(path.str, path.len); + return new dt_torrent(path_sv); + } + catch (...) { + return nullptr; + } +} + +extern "C" +void dt_free_torrent_read (struct dt_torrent* t) { + delete t; +} + +extern "C" +void dt_print_to_stdout(struct dt_torrent* t) { + try { + t->reader.print(std::cout); + } + catch (...) { + } +} + +extern "C" +size_t dt_read_piece_length(struct dt_torrent* t) { + try { + return t->reader.read_piece_length(); + } + catch (...) { + return -1; + } +} + +extern "C" +int_fast32_t dt_read_file_count(struct dt_torrent* t) { + try { + return t->reader.read_file_count(); + } + catch (...) { + return -1; + } +} + +extern "C" +dt_string_list dt_read_file_path(struct dt_torrent* t, size_t index) { + try { + auto vec = t->reader.read_file_path(index); + t->vec_buff.clear(); + t->vec_buff.reserve(vec.size()); + for (std::string_view s : vec) { + t->vec_buff.emplace_back(s.data(), s.size()); + } + return {t->vec_buff.data(), t->vec_buff.size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +dt_string dt_read_joint_file_path(struct dt_torrent* t, size_t index, char sep){ + try { + t->str_buff = t->reader.read_joint_file_path(index, sep); + return {t->str_buff.c_str(), t->str_buff.size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +size_t dt_read_file_size(struct dt_torrent* t, size_t index) { + try { + return t->reader.read_file_size(index); + } + catch (...) { + return -1; + } +} + +extern "C" +int dt_read_is_private(struct dt_torrent* t) { + try { + return static_cast(t->reader.read_is_private()); + } + catch (...) { + return -1; + } +} + +extern "C" +dt_string dt_read_comment(struct dt_torrent* t) { + try { + std::string_view comment = t->reader.read_comment(); + return dt_string{comment.data(), comment.size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +dt_string dt_read_hashes(struct dt_torrent* t) { + try { + std::string_view hashes = t->reader.read_hashes(); + return dt_string{hashes.data(), hashes.size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +dt_string dt_read_created_by(struct dt_torrent* t) { + try { + std::string_view created = t->reader.read_created_by(); + return dt_string{created.data(), created.size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +dt_string dt_read_announce(struct dt_torrent* t) { + try { + std::string_view announce = t->reader.read_announce(); + return dt_string{announce.data(), announce.size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +size_t dt_read_announce_list_size(struct dt_torrent* t) { + try { + return t->reader.read_announce_list_size(); + } + catch (...) { + return -1; + } +} + +extern "C" +dt_string_list dt_read_announce_list(struct dt_torrent* t, size_t index) { + try { + if (t->announce_list_buff.empty()) { + t->announce_list_buff.reserve(t->reader.read_announce_list_size()); + for (const auto& sublist : t->reader.read_announce_list()) { + t->announce_list_buff.emplace_back(); + for (std::string_view item : sublist) { + t->announce_list_buff.back().emplace_back(item.data(), item.size()); + } + } + } + return {t->announce_list_buff[index].data(), t->announce_list_buff[index].size()}; + } + catch (...) { + return {nullptr, 0}; + } +} + +extern "C" +time_t dt_read_creation_date(struct dt_torrent* t) { + try { + return t->reader.read_creation_date(); + } + catch (...) { + return {}; + } +} diff --git a/src/parser.hpp b/src/ducktorrent/include/ducktorrent/parser.hpp similarity index 100% rename from src/parser.hpp rename to src/ducktorrent/include/ducktorrent/parser.hpp diff --git a/src/ducktorrent/include/ducktorrent/torrent_read.h b/src/ducktorrent/include/ducktorrent/torrent_read.h new file mode 100644 index 0000000..c60d9d0 --- /dev/null +++ b/src/ducktorrent/include/ducktorrent/torrent_read.h @@ -0,0 +1,85 @@ +/* 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 . + */ + +#pragma once + +//c wrapper for torrent_read.hpp + +#if defined(__cplusplus) +extern "C" { +#endif + +#include +#include +#include + +/* Strings and lists returned by functions in this file should be considered + * temporary and might become invalid on any subsequent function call. There is + * no thread safety mechanism so you should protect calls with your own mutex. + * With that said, if you're interested in avoiding unnecessary memory + * allocations and copies, there are cases where the above is not true. Rather + * than making a list of functions the whose output is guaranteed to not change, + * the rule for figuring out what might not need copying is the following: + * wrapper functions around C++ methods that return an std::string_view do not + * need to be copied. That is because the string returned by the C++ side is + * already a reference into the in-memory torrent file buffer. This however + * means that these particular strings are *not* null-terminated. + * As these functions do not write to the C wrapper's internal string cache and + * they only read from the parsed tree, it is also safe to call them in a + * multithreaded context. + * One more exception to the above rule is the special case of + * dt_read_announce_list(), since it returs a list of lists this C wrapper + * caches the whole thing on the first call and subsequent calls will not hit + * the C++ side again. As said, the list side of things is cached in the C + * wrapper, and since the inner type returned by the C++ method is + * std::string_view this means the strings won't change either, therefore it is + * safe to not copy these items either for as long as the dt_torrent context + * stays alive. + */ + +typedef struct { + const char* str; + size_t len; +} dt_string; + +typedef struct { + const dt_string* lst; + size_t len; +} dt_string_list; + +/*struct dt_torrent_read* dt_make_reader_from_data (const char* raw_data, size_t raw_data_size);*/ +struct dt_torrent* dt_make_reader_from_path (dt_string path); +void dt_free_torrent_read (struct dt_torrent*); + +void dt_print_to_stdout(struct dt_torrent*); +size_t dt_read_piece_length(struct dt_torrent*); +int_fast32_t dt_read_file_count(struct dt_torrent*); +dt_string_list dt_read_file_path(struct dt_torrent*, size_t index); +dt_string dt_read_joint_file_path(struct dt_torrent*, size_t index, char sep); +size_t dt_read_file_size(struct dt_torrent*, size_t index); +int dt_read_is_private(struct dt_torrent*); +dt_string dt_read_comment(struct dt_torrent*); +dt_string dt_read_hashes(struct dt_torrent*); +dt_string dt_read_created_by(struct dt_torrent*); +dt_string dt_read_announce(struct dt_torrent*); +size_t dt_read_announce_list_size(struct dt_torrent*); +dt_string_list dt_read_announce_list(struct dt_torrent*, size_t index); +time_t dt_read_creation_date(struct dt_torrent*); + +#if defined(__cplusplus) +} +#endif diff --git a/src/torrent_read.hpp b/src/ducktorrent/include/ducktorrent/torrent_read.hpp similarity index 98% rename from src/torrent_read.hpp rename to src/ducktorrent/include/ducktorrent/torrent_read.hpp index f37ccfa..9e9e151 100644 --- a/src/torrent_read.hpp +++ b/src/ducktorrent/include/ducktorrent/torrent_read.hpp @@ -55,6 +55,7 @@ public: std::string_view read_hashes() const; std::string_view read_created_by() const; std::string_view read_announce() const; + std::size_t read_announce_list_size() const; std::vector> read_announce_list() const; std::time_t read_creation_date() const; diff --git a/src/ducktorrent/meson.build b/src/ducktorrent/meson.build new file mode 100644 index 0000000..1fee3c3 --- /dev/null +++ b/src/ducktorrent/meson.build @@ -0,0 +1,34 @@ +pub_inc = include_directories('include') + +should_install = not meson.is_subproject() or get_option('default_library')=='shared' + +ducktorrent_target = library(meson.project_name(), + 'parser.cpp', + 'split.cpp', + 'torrent_read.cpp', + 'c_api_torrent_read.cpp', + 'visitors/debug_visitor.cpp', + 'visitors/find_t_visitor.cpp', + dependencies: [ + boost_dep, + ], + include_directories: [ + pub_inc, + ], + install: should_install, +) + +ducktorrent_dep = declare_dependency( + include_directories: [ + pub_inc, + ], + link_with: ducktorrent_target, +) + +if should_install + install_headers( + 'include/ducktorrent/torrent_read.h', + 'include/ducktorrent/torrent_read.hpp', + 'include/ducktorrent/parser.hpp', + ) +endif diff --git a/src/parser.cpp b/src/ducktorrent/parser.cpp similarity index 99% rename from src/parser.cpp rename to src/ducktorrent/parser.cpp index 302d84a..2aedfa2 100644 --- a/src/parser.cpp +++ b/src/ducktorrent/parser.cpp @@ -19,7 +19,7 @@ //# define BOOST_SPIRIT_X3_DEBUG #endif -#include "parser.hpp" +#include "ducktorrent/parser.hpp" #include #include diff --git a/src/split.cpp b/src/ducktorrent/split.cpp similarity index 100% rename from src/split.cpp rename to src/ducktorrent/split.cpp diff --git a/src/split.hpp b/src/ducktorrent/split.hpp similarity index 100% rename from src/split.hpp rename to src/ducktorrent/split.hpp diff --git a/src/torrent_read.cpp b/src/ducktorrent/torrent_read.cpp similarity index 97% rename from src/torrent_read.cpp rename to src/ducktorrent/torrent_read.cpp index 92c3532..4894795 100644 --- a/src/torrent_read.cpp +++ b/src/ducktorrent/torrent_read.cpp @@ -15,8 +15,8 @@ * along with ducktorrent. If not, see . */ -#include "torrent_read.hpp" -#include "parser.hpp" +#include "ducktorrent/torrent_read.hpp" +#include "ducktorrent/parser.hpp" #include "visitors/debug_visitor.hpp" #include "visitors/find_t_visitor.hpp" #include @@ -231,6 +231,10 @@ std::string_view TorrentRead::read_announce() const { return find_string("/announce", m_parsed_values); } +std::size_t TorrentRead::read_announce_list_size() const { + return find_int("/announce/[[size]]", m_parsed_values); +} + std::vector> TorrentRead::read_announce_list() const { typedef boost::spirit::x3::forward_ast> TorrentListType; diff --git a/src/visitors/debug_visitor.cpp b/src/ducktorrent/visitors/debug_visitor.cpp similarity index 100% rename from src/visitors/debug_visitor.cpp rename to src/ducktorrent/visitors/debug_visitor.cpp diff --git a/src/visitors/debug_visitor.hpp b/src/ducktorrent/visitors/debug_visitor.hpp similarity index 97% rename from src/visitors/debug_visitor.hpp rename to src/ducktorrent/visitors/debug_visitor.hpp index a93b867..534c6f2 100644 --- a/src/visitors/debug_visitor.hpp +++ b/src/ducktorrent/visitors/debug_visitor.hpp @@ -17,7 +17,7 @@ #pragma once -#include "../parser.hpp" +#include "ducktorrent/parser.hpp" #include #include diff --git a/src/visitors/find_t_visitor.cpp b/src/ducktorrent/visitors/find_t_visitor.cpp similarity index 100% rename from src/visitors/find_t_visitor.cpp rename to src/ducktorrent/visitors/find_t_visitor.cpp diff --git a/src/visitors/find_t_visitor.hpp b/src/ducktorrent/visitors/find_t_visitor.hpp similarity index 99% rename from src/visitors/find_t_visitor.hpp rename to src/ducktorrent/visitors/find_t_visitor.hpp index 8e8101d..8e9e694 100644 --- a/src/visitors/find_t_visitor.hpp +++ b/src/ducktorrent/visitors/find_t_visitor.hpp @@ -17,7 +17,7 @@ #pragma once -#include "../parser.hpp" +#include "ducktorrent/parser.hpp" #include #include #include diff --git a/src/meson.build b/src/meson.build index 1f96449..10941c5 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,13 +1,2 @@ -executable(meson.project_name(), - 'main.cpp', - 'parser.cpp', - 'split.cpp', - 'torrent_read.cpp', - 'visitors/debug_visitor.cpp', - 'visitors/find_t_visitor.cpp', - dependencies: [ - boost_dep, - libstriezel_dep, - ], - install: true, -) +subdir('ducktorrent') +subdir('cli')