#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include <libtorrent/info_hash.hpp>
#include <libtorrent/session.hpp>
namespace torlinkc {
struct TorrentProgress {
double progress = 0.0; // 0..1
std::int64_t downloaded = 0;
std::int64_t total = 0;
int speed = 0; // bytes/sec, download
int uploadSpeed = 0; // bytes/sec
std::int64_t uploaded = 0;
int peers = 0;
double timeRemaining = 0.0; // ms; +infinity when unknown, matching the TS original
std::string name;
};
struct TorrentMeta {
std::string name;
std::int64_t total = 0;
int files = 0;
// Raw bencoded .torrent bytes, available once metadata arrives. Persisted so
// a later re-seed can verify the on-disk file without re-fetching metadata
// from the swarm (which a bare magnet would require).
std::optional<std::string> torrentFile;
};
struct AddHandlers {
std::function<void(const TorrentMeta&)> onMetadata;
std::function<void()> onDone;
std::function<void(const std::string&)> onError;
};
// Wraps a single lt::session. `source` accepted by add() is a magnet URI, a
// bare info hash, or a path to a .torrent file -- mirroring engine.ts's
// contract with webtorrent's client.add(). Ported from download/engine.ts;
// where the original polled webtorrent's async getters defensively, this
// polls libtorrent's synchronous torrent_handle::status() instead (Phase 2
// switches metadata/done/error over to the alert-driven push model; for
// Phase 1's console harness, pollAlerts() is called synchronously from the
// caller's own tick loop).
class TorrentEngine {
public:
TorrentEngine();
~TorrentEngine();
TorrentEngine(const TorrentEngine&) = delete;
TorrentEngine& operator=(const TorrentEngine&) = delete;
void add(const std::string& id, const std::string& source, const std::string& dir, AddHandlers handlers,
const std::vector<std::string>& announce = {});
// Drains pending libtorrent alerts and dispatches metadata/done/error to the
// handlers registered in add(). Must be called periodically (the queue's
// tick() does this) or those callbacks never fire.
void pollAlerts();
std::optional<TorrentProgress> stats(const std::string& id);
void remove(const std::string& id);
void destroy();
// The port the session accepts incoming peers on (diagnostics / tests).
int listenPort() const;
private:
struct Entry {
lt::torrent_handle handle;
AddHandlers handlers;
};
std::unique_ptr<lt::session> session_;
std::unordered_map<std::string, Entry> torrents_; // our id -> entry
std::unordered_map<lt::sha1_hash, std::string> infoHashToId_; // reverse lookup for alert dispatch
void removeEntry(const std::string& id);
};
} // namespace torlinkc