Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ These are load-bearing and enforced in the code:

## API at a glance

56 public `bt*` handlers (full signatures in **[api-reference](docs/api-reference.md)**):
58 public `bt*` handlers (full signatures in **[api-reference](docs/api-reference.md)**):

| Group | Handlers |
|---|---|
Expand All @@ -161,7 +161,7 @@ These are load-bearing and enforced in the code:
| Priorities / limits | `btSetFilePriority` · `btSetFilePriorities` · `btSetPiecePriority` · `btSetTorrentLimits` · `btSetMaxConnections` · `btSetMaxUploads` |
| Flags / modes | `btSetTorrentFlags` · `btUnsetTorrentFlags` · `btSetSequentialDownload` · `btSetAutoManaged` · `btSetSuperSeeding` · `btSetShareMode` · `btSetUploadMode` |
| Queue / storage | `btQueuePosition` · `btQueueUp` · `btQueueDown` · `btQueueTop` · `btQueueBottom` · `btMoveStorage` |
| Inspect | `btTorrentStatus` · `btTorrentCount` · `btTorrentHandleAt` · `btInfoHash` · `btPieceBitfield` · `btPeerList` |
| Inspect | `btTorrentStatus` · `btTorrentCount` · `btTorrentHandleAt` · `btInfoHash` · `btPieceBitfield` · `btPeerList` · `btFileList` · `btPieceAvailability` |
| Events | `btPoll` |
| DHT | `btDhtAddBootstrap` · `btDhtState` · `btDhtSaveState` · `btDhtLoadState` |
| DHT key-value (BEP44) | `btDhtKeypair` · `btDhtPutImmutable` · `btDhtGetImmutable` · `btDhtPutMutable` · `btDhtGetMutable` |
Expand Down
25 changes: 25 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,22 @@ The connected peers as a `List` of `Array`s, each keyed by the **peer keys**
Empty `List` on a bad handle.
- **Usage:** function - `repeat for each element tPeer in btPeerList(tH)`.

### `btFileList(in pTorrent as Integer) returns List`
The torrent's files as a `List` of `Array`s, one per file, each with keys `path`
(relative path within the torrent), `size` (bytes), `progress` (bytes of that
file downloaded), and `priority` (`0..7`). The whole file table in **one FFI
round-trip**. Empty `List` until metadata arrives (a magnet has no file table
yet) or on a bad handle - so it doubles as a "do we have metadata" probe.
- **Usage:** function - `repeat for each element tFile in btFileList(tH)` then read `tFile["path"]`, `tFile["size"]`, `tFile["progress"]`, `tFile["priority"]`.

### `btPieceAvailability(in pTorrent as Integer) returns Data`
Per-piece availability as raw `Data`: **one byte per piece**, the number of
connected peers advertising that piece (clamped to `255`). A read-only view for
an availability/rarity grid - pair it with `btPieceBitfield` (which pieces you
have) for a full piece map. Empty `Data` until the torrent has metadata and
peers, or on a bad handle.
- **Usage:** function - `put btPieceAvailability(tH) into tAvail`, then `byte i of tAvail`.

---

## Events / poll
Expand Down Expand Up @@ -502,6 +518,15 @@ arithmetic.
| `progress` | 44 | real | peer's completion fraction, `0..1` |
| `flags` | 45 | int | peer flag bits |

### File entry (`btFileList`, one array per file)

| key | field id | type | meaning |
|---|---|---|---|
| `path` | 120 | utf8 | file path within the torrent (relative) |
| `size` | 121 | int | file size, bytes |
| `progress` | 122 | int | bytes of this file downloaded |
| `priority` | 123 | int | this file's download priority, `0..7` |

### DHT state (`btDhtState`)

| key | field id | type | meaning |
Expand Down
12 changes: 11 additions & 1 deletion src/btx_abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extern "C" {
* signature, a new record fieldId or alert code, or a framing change. The LCB
* layer hard-codes the matching number in checkABI() and refuses to run on
* skew. Start at 1. */
#define BTX_ABI_VERSION 4
#define BTX_ABI_VERSION 5

/* ----------------------------------------------------------- export linkage */

Expand Down Expand Up @@ -226,6 +226,16 @@ BTX_API int BTX_CALL btx_piece_bitfield(int t, void *out, int cap);
/* Connected peers as a count-prefixed list of KV records (schema §). */
BTX_API int BTX_CALL btx_peer_list(int t, void *out, int cap);

/* The torrent's files as a count-prefixed list of KV records — one per file:
* path (relative), size, bytes downloaded, and download priority. Empty (count
* 0) until metadata arrives. One round-trip for the whole file table (§8). */
BTX_API int BTX_CALL btx_file_list(int t, void *out, int cap);

/* Per-piece availability (how many connected peers advertise each piece) as raw
* bytes — one byte per piece, clamped to 255 — a read-only view for an
* availability grid. bytes-written / -needed / 0, like btx_piece_bitfield. */
BTX_API int BTX_CALL btx_piece_availability(int t, void *out, int cap);

/* ====================================================================== *
* The alert drain (the event firehose) — one FFI round-trip per poll (§3)
* ====================================================================== */
Expand Down
8 changes: 7 additions & 1 deletion src/btx_record.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,13 @@ enum FieldId : uint8_t {
F_DHT_AUTHORITATIVE = 110, /* int 0/1: mutable get response is authoritative */
F_DHT_NUM_SUCCESS = 111, /* int: nodes that accepted a put */
F_DHT_SECRET_KEY = 112, /* hex: 64-byte ed25519 secret key */
F_DHT_SEED = 113 /* hex: 32-byte ed25519 seed (persist to keep identity) */
F_DHT_SEED = 113, /* hex: 32-byte ed25519 seed (persist to keep identity) */

/* ---- file entry (120..139): one record per file in btx_file_list ---- */
F_FILE_PATH = 120, /* utf8: file path within the torrent (relative) */
F_FILE_SIZE = 121, /* int (64-bit): file size in bytes */
F_FILE_PROGRESS = 122, /* int (64-bit): bytes of this file downloaded */
F_FILE_PRIORITY = 123 /* int 0..7: this file's download priority */
};

/* ------------------------------------------------------------- alert codes */
Expand Down
78 changes: 77 additions & 1 deletion src/torrent.lcb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ metadata title is "TorrentXT"
-- ===================================================================== --

-- Must equal BTX_ABI_VERSION in src/btx_abi.h; _checkABI() throws on skew.
constant kABIVersion is 4
constant kABIVersion is 5

-- libfoundation MCStringEncoding value for UTF-8 (ASCII=0, Windows1252=1,
-- MacRoman=2, ISO8859_1=3, UTF8=4). Used by _decodeText via MCStringDecode.
Expand Down Expand Up @@ -139,6 +139,12 @@ constant kFieldDhtNumSuccess is 111
constant kFieldDhtSecretKey is 112
constant kFieldDhtSeed is 113

-- file entry fields (one record per file from btFileList)
constant kFieldFilePath is 120
constant kFieldFileSize is 121
constant kFieldFileProgress is 122
constant kFieldFilePriority is 123

-- Stable alert type codes (mirror btx_record.h AlertType -> kAlert*).
constant kAlertTorrentAdded is 1
constant kAlertMetadataReceived is 2
Expand Down Expand Up @@ -231,6 +237,8 @@ private foreign handler _btx_torrent_handle_at(in pS as CInt, in pIndex as CInt)
private foreign handler _btx_info_hash_hex(in pT as CInt, in pOut as Pointer, in pCap as CInt) returns CInt binds to "c:torrentxt>btx_info_hash_hex!cdecl"
private foreign handler _btx_piece_bitfield(in pT as CInt, in pOut as Pointer, in pCap as CInt) returns CInt binds to "c:torrentxt>btx_piece_bitfield!cdecl"
private foreign handler _btx_peer_list(in pT as CInt, in pOut as Pointer, in pCap as CInt) returns CInt binds to "c:torrentxt>btx_peer_list!cdecl"
private foreign handler _btx_file_list(in pT as CInt, in pOut as Pointer, in pCap as CInt) returns CInt binds to "c:torrentxt>btx_file_list!cdecl"
private foreign handler _btx_piece_availability(in pT as CInt, in pOut as Pointer, in pCap as CInt) returns CInt binds to "c:torrentxt>btx_piece_availability!cdecl"

private foreign handler _btx_pop_alerts(in pS as CInt, in pOut as Pointer, in pCap as CInt) returns CInt binds to "c:torrentxt>btx_pop_alerts!cdecl"

Expand Down Expand Up @@ -527,6 +535,14 @@ private handler _fieldKey(in pId as Integer) returns String
return "secretKey"
else if pId is kFieldDhtSeed then
return "seed"
else if pId is kFieldFilePath then
return "path"
else if pId is kFieldFileSize then
return "size"
else if pId is kFieldFileProgress then
return "progress"
else if pId is kFieldFilePriority then
return "priority"
else
return ""
end if
Expand Down Expand Up @@ -1140,6 +1156,66 @@ public handler btPeerList(in pTorrent as Integer) returns List
return tPeers
end handler

-- The torrent's files as a List of Arrays (one per file), each with keys
-- "path", "size", "progress" (bytes downloaded) and "priority" (0..7). Empty
-- until metadata arrives. One FFI round-trip returns the whole file table.
public handler btFileList(in pTorrent as Integer) returns List
variable tFiles as List
variable tCount as Integer
variable tOffset as Integer
variable tFileCount as Integer
variable tBodyLen as Integer
variable tFile as Array
variable tI as Integer
variable tBuf as Data
put [] into tFiles
_ensureStatus(kStatusCap)
unsafe
put _btx_file_list(pTorrent, sStatusPtr, sStatusCap) into tCount
end unsafe
if tCount < 0 then
_ensureStatus(-tCount)
unsafe
put _btx_file_list(pTorrent, sStatusPtr, sStatusCap) into tCount
end unsafe
end if
if tCount <= 0 then
return tFiles
end if
put _bufToData(sStatusPtr, tCount) into tBuf
put 1 into tOffset
put _readU16(tBuf, tOffset) into tFileCount
add 2 to tOffset
repeat with tI from 1 up to tFileCount
-- entry := [bodyLen:u16][kvrecord]; advance by the on-wire bodyLen
put _readU16(tBuf, tOffset) into tBodyLen
put _parseRecord(tBuf, tOffset + 2) into tFile
push tFile onto tFiles
add (2 + tBodyLen) to tOffset
end repeat
return tFiles
end handler

-- Per-piece availability: one byte per piece (how many connected peers have
-- that piece, clamped to 255). Empty data until the torrent has metadata+peers.
public handler btPieceAvailability(in pTorrent as Integer) returns Data
variable tCount as Integer
_ensureStatus(kStatusCap)
unsafe
put _btx_piece_availability(pTorrent, sStatusPtr, sStatusCap) into tCount
end unsafe
if tCount < 0 then
_ensureStatus(-tCount)
unsafe
put _btx_piece_availability(pTorrent, sStatusPtr, sStatusCap) into tCount
end unsafe
end if
if tCount <= 0 then
return the empty data
end if
return _bufToData(sStatusPtr, tCount)
end handler

-- ===================================================================== --
-- Public API: the alert drain (the event firehose)
-- ===================================================================== --
Expand Down
89 changes: 89 additions & 0 deletions src/torrent_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1168,6 +1168,95 @@ extern "C" BTX_API int BTX_CALL btx_peer_list(int t, void *out, int cap) {
});
}

/* ====================================================================== *
* Inspection — file table & piece availability (ABI v5)
* ====================================================================== */

extern "C" BTX_API int BTX_CALL btx_file_list(int t, void *out, int cap) {
BTX_GUARD_BUFFER({
bool ok = false; lt::torrent_handle h = torrent_only(t, nullptr, &ok);
if (!ok) return 0; /* stale -> empty */

/* The file table lives in the metainfo, which is null for a magnet until
* metadata arrives — that is a legitimately EMPTY list (count 0), not an
* error. file list := [fileCount:u16] then fileCount x [bodyLen:u16]
* [kvrecord], mirroring the peer-list framing so the LCB walker reuses
* the same byte arithmetic. */
std::shared_ptr<const lt::torrent_info> ti = h.torrent_file();

btx::RecordWriter w(out, cap);
const size_t countAt = w.pos();
w.put_u16(0); /* fileCount placeholder */
uint16_t emitted = 0;

if (ti) {
const lt::file_storage &fs = ti->files();
const int n = fs.num_files();
/* per-file downloaded bytes + priorities in two bulk calls (cheaper
* than N round-trips); tiny control data, never payload. */
std::vector<std::int64_t> prog;
h.file_progress(prog);
std::vector<lt::download_priority_t> prio = h.get_file_priorities();
for (int i = 0; i < n; ++i) {
lt::file_index_t fi{i};
const size_t bodyAt = w.pos();
w.put_u16(0); /* bodyLen placeholder */
const size_t bodyStart = w.pos();
{
btx::KVRecord r(w);
r.put_str(btx::F_FILE_PATH, fs.file_path(fi));
r.put_int(btx::F_FILE_SIZE,
static_cast<long long>(fs.file_size(fi)));
r.put_int(btx::F_FILE_PROGRESS,
static_cast<size_t>(i) < prog.size()
? static_cast<long long>(prog[static_cast<size_t>(i)])
: 0);
r.put_int(btx::F_FILE_PRIORITY,
static_cast<size_t>(i) < prio.size()
? static_cast<long long>(static_cast<std::uint8_t>(
prio[static_cast<size_t>(i)]))
: 0);
r.finish();
}
w.patch_u16(bodyAt, static_cast<uint16_t>(w.pos() - bodyStart));
++emitted;
}
}
w.patch_u16(countAt, emitted);

if (w.overflow()) return -static_cast<int>(w.pos());
return static_cast<int>(w.pos());
});
}

extern "C" BTX_API int BTX_CALL btx_piece_availability(int t, void *out, int cap) {
BTX_GUARD_BUFFER({
bool ok = false; lt::torrent_handle h = torrent_only(t, nullptr, &ok);
if (!ok) return 0; /* stale -> empty */

/* One int per piece (peers advertising it); we hand back one byte each,
* clamped to 255 — a read-only availability VIEW, not payload. Empty when
* the torrent has no metadata / no peers yet (legitimately 0 bytes). */
std::vector<int> avail;
h.piece_availability(avail);
const size_t nbytes = avail.size();
if (nbytes == 0) return 0;

uint8_t *dst = static_cast<uint8_t *>(out);
const bool fits = (dst != nullptr) && (cap > 0) &&
(nbytes <= static_cast<size_t>(cap));
if (fits) {
for (size_t i = 0; i < nbytes; ++i) {
int a = avail[i];
dst[i] = static_cast<uint8_t>(a < 0 ? 0 : (a > 255 ? 255 : a));
}
}
if (nbytes > static_cast<size_t>(cap < 0 ? 0 : cap))
return -static_cast<int>(nbytes);
return static_cast<int>(nbytes);
});
}

/* ====================================================================== *
* The alert drain (the event firehose) — one FFI round-trip per poll (§3)
* ====================================================================== */
Expand Down
2 changes: 2 additions & 0 deletions tests/torrent_smoke_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ static void test_bogus_handles_are_noops() {
CHECK(btx_info_hash_hex(h, buf, sizeof buf) == 0);
CHECK(btx_piece_bitfield(h, buf, sizeof buf) == 0);
CHECK(btx_peer_list(h, buf, sizeof buf) == 0);
CHECK(btx_file_list(h, buf, sizeof buf) == 0); /* ABI v5 */
CHECK(btx_piece_availability(h, buf, sizeof buf) == 0); /* ABI v5 */
}

/* remove() needs a live session to even reach the torrent check; with one
Expand Down
Loading