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')