#ifndef SWARMD_CONTENT_H
#define SWARMD_CONTENT_H
// The content plane: fetch and serve content-addressed files over liboverlay.
//
// ⛔ BULK BYTES NEVER CROSS THE LOCAL SOCKET (docs/SWARMD-DESIGN.md §6). A .res
// is 108 MB. The socket carries a 32-byte hash and a path; the overlay writes
// the file and the client opens it. That is also what makes a fetch resumable
// across a daemon restart, because the partial and its journal are on disk.
//
// THREADING, which is the same rule the rest of the daemon follows and the same
// one src/overlay_node.h arrived at for Godot:
//
// UDX thread owns the Protocol OUTRIGHT. Protomux dispatches inbound overlay
// frames there, and protomux/udx handles belong to that loop, so
// both halves must live on it or they race. Every command from a
// client is marshalled in with run_on_loop.
// main thread drains an event queue and writes to client sockets. It never
// touches the Protocol — not even to read a counter.
//
// The one exception is hash_of_file(), which touches no overlay state at all: it
// streams a file through the chunker. It runs where it is called.
#include <cstdint>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "../../src/overlay_transport.h"
#include "../../src/udx_transport.h"
#include "overlay/file_index.h"
#include "overlay/protocol.h"
namespace swarmd {
class Content {
public:
struct Event {
enum Kind { DONE, FAILED, PROGRESS } kind;
std::string hash;
std::string detail; // path for DONE, reason for FAILED
uint64_t received = 0;
uint64_t total = 0;
};
// `storage_dir` holds the index log, the journals and the partials. It must
// survive restarts — that is what makes a resumed download cheap.
bool open(const std::string &storage_dir, UdxTransport *transport);
void close();
bool is_open() const { return open_; }
// The content hash of a file, computed the way the overlay computes it:
// blake2b over the hypercore leaf construction, not a plain digest. Empty on
// failure. Streams, so a file larger than RAM is not a different path.
static std::string hash_of_file(const std::string &path);
// All of these return immediately; results arrive as events.
void serve(const std::string &hash, const std::string &path);
void fetch(const std::string &hash, const std::string &dest_path);
void cancel(const std::string &hash, bool discard);
// UDX THREAD ONLY. Called from the daemon's mux callbacks — swarmd owns the
// peer lifecycle, so it builds the overlay channels itself rather than
// letting a second component take set_mux_callbacks.
void add_peer(int32_t peer_id, Protomux *mux);
void remove_peer(int32_t peer_id);
// MAIN THREAD. Advances liboverlay's clock (it never calls now() itself, so
// without this no watchdog fires and the library is correct and inert),
// samples progress, and hands back everything that has happened.
void poll(std::vector<Event> &out);
private:
void _push(const Event &e);
std::unique_ptr<hyperoverlay::index::FileIndex> index_;
std::unique_ptr<hyperoverlay::Protocol> proto_;
UdxTransport *t_ = nullptr;
bool open_ = false;
std::string storage_dir_;
// UDX THREAD ONLY: one overlay channel per connection.
std::map<int32_t, hyperswarm::OverlayTransport *> peers_;
std::mutex m_;
std::deque<Event> events_;
// Bounded. A fetch that reports faster than the socket drains must drop
// PROGRESS — never a terminal event, because losing one strands a client
// waiting for a message that never comes.
static constexpr size_t MAX_EVENTS = 4096;
uint64_t dropped_progress_ = 0;
uint64_t last_progress_ms_ = 0;
};
} // namespace swarmd
#endif