From 753c20411033cbebd87f7a9eaf9413cbf5b7238b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 27 Jun 2026 04:25:41 +0000 Subject: [PATCH] docs: bring documentation current; trim to two flagship examples Documentation pass to match the project as it stands (ABI v8, 75 public handlers) and slim the examples to the two flagship demos. Examples: - Remove the three minimal single-concept demos: torrent-demo, torrent-dht-note, torrent-dht-channel. Keep the full client (torrent-client), the decentralized DHT demo (torrent-dht-channels), and the shared poll-dispatcher utility (torrent-helpers); both flagships are self-contained and run without the helper. - package-extension.py now stages the binding + helpers + both flagship demos (it had bundled the now-removed torrent-demo and omitted the flagships). Docs: - README: Features rewritten to cover the v4-v8 surface (queue, flags/modes, move-storage, scrape, caps, file table, per-piece availability, trackers, web seeds, session ops, IP filter, streaming deadlines, extended add); Examples list trimmed to the two flagships + helpers; Status notes the 75-handler / ABI v8 breadth. API-at-a-glance table already current (75). - getting-started: drop references to the removed torrent-demo (the skeleton is presented standalone; it points at the two flagship examples instead). - architecture: add the channels demo to the stack diagram; list the new file/tracker/availability getters among the out-buffer crossings. - implementation-plan: fix the examples file tree. Verified: api-reference documents all 75 public handlers (coverage checked); no dangling references to the removed files anywhere in the repo; check-livecodescript passes (4 files); package-extension.py parses. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01371AXB4CUUke7enHHS8okc --- README.md | 74 +++-- docs/TorrentXT-IMPLEMENTATION-PLAN.md | 6 +- docs/architecture.md | 10 +- docs/getting-started.md | 11 +- examples/torrent-demo.livecodescript | 130 -------- examples/torrent-dht-channel.livecodescript | 316 -------------------- examples/torrent-dht-note.livecodescript | 265 ---------------- tools/package-extension.py | 10 +- 8 files changed, 62 insertions(+), 760 deletions(-) delete mode 100644 examples/torrent-demo.livecodescript delete mode 100644 examples/torrent-dht-channel.livecodescript delete mode 100644 examples/torrent-dht-note.livecodescript diff --git a/README.md b/README.md index b8475a0..07004e8 100644 --- a/README.md +++ b/README.md @@ -27,23 +27,34 @@ runtime. ## Features - **Add anything** — magnet links, `.torrent` files, and resume data; metadata is - fetched over the swarm for magnets. -- **Full control** — pause, resume, force-recheck, force-reannounce, remove (with or - without deleting data). + fetched over the swarm for magnets. Optional **add-time flags** (e.g. add + *paused* to set priorities before it starts, or *sequential* for streaming). +- **Full control** — pause, resume, force-recheck, force-reannounce, scrape, + move-storage, clear-error, remove (with or without data), and **download-queue** + positioning (up / down / top / bottom). +- **Modes** — per-torrent `torrent_flags`: sequential download (streaming), + auto-managed, super-seeding, share-mode, upload-only. - **Seeding & creation** — build a `.torrent` from a file or folder and seed it. -- **Tuning** — per-file and per-piece priorities, per-torrent and session-wide rate - limits, and the full libtorrent `settings_pack` surface. -- **Networking** — DHT (BEP 5) with bootstrap and saved state, Local Service - Discovery, PEX, uTP, UPnP/NAT-PMP, and MSE/PE connection encryption. +- **Tuning** — per-file and per-piece priorities, per-torrent rate caps and + connection / upload-slot caps, the full libtorrent `settings_pack` surface, an + **IP filter** (block ranges / blocklists), and **streaming piece-deadlines**. +- **Networking** — DHT (BEP 5) with bootstrap, saved state and peer announce, + Local Service Discovery, PEX, uTP, UPnP/NAT-PMP, MSE/PE encryption; plus + whole-session pause/resume, the bound listen port, and find-by-info-hash. - **DHT key-value store (BEP44)** — put/get small signed (mutable) or content-addressed (immutable) values: a server-less rendezvous / identity layer. - **Inspection** — live status snapshots (state, progress, rates, peers, ETA), the - peer list, and the piece-completion bitfield. + peer list, the piece-completion bitfield and per-piece availability, the **file + table** (names, sizes, per-file progress and priority), the **tracker list**, + and **web seeds**. +- **Trackers & web seeds** — list and edit a torrent's announce list and its + HTTP/URL seeds (BEP 19) at runtime. - **Persistence** — save and reload fast-resume data so a partial download survives a restart. - **Events, not callbacks** — inbound activity (metadata received, piece finished, - torrent finished, tracker replies, errors) arrives as ordinary message-path - handlers via a poll-drained queue, never from a foreign thread. + torrent finished, tracker replies, scrape/storage results, errors) arrives as + ordinary message-path handlers via a poll-drained queue, never from a foreign + thread. ## Platform support @@ -172,29 +183,26 @@ These are load-bearing and enforced in the code: ## Examples +Two runnable flagship demos plus the shared poll-dispatcher utility: + - **[`examples/torrent-client.livecodescript`](examples/torrent-client.livecodescript)** - — the flagship: a self-building, multi-torrent client with a smart Add box + — the flagship client: a self-building, multi-torrent app with a smart Add box (magnet / `.torrent` / HTTP / info-hash), per-torrent controls, create-and-seed, a live color-coded table with inline progress bars, DHT bootstrap, and an event log. -- **[`examples/torrent-demo.livecodescript`](examples/torrent-demo.livecodescript)** - — the minimal add-a-magnet-and-watch-it-finish skeleton the getting-started guide - walks through. -- **[`examples/torrent-helpers.livecodescript`](examples/torrent-helpers.livecodescript)** - — the poll dispatcher (`btStartPolling` / `btStopPolling`) and formatting sugar. - **[`examples/torrent-dht-channels.livecodescript`](examples/torrent-dht-channels.livecodescript)** - — the flagship **multi-machine demo**: a fully decentralized "channel" app that + — the flagship **multi-machine DHT demo**: a fully decentralized "channel" app that marries the DHT and BitTorrent. Publish a file to *your* channel (it creates, seeds, and announces the magnet under your ed25519 key on the DHT); follow other people's channel addresses and one-click **download** their latest release while - they seed — no server anywhere. Includes a live transfers table and an immutable - "quick drop" (pin text, share a 40-char code). The DHT says *where*, BitTorrent - moves *what*. -- **[`examples/torrent-dht-note.livecodescript`](examples/torrent-dht-note.livecodescript)** - — a minimal single-concept reference for the BEP44 *immutable* side: pin a short - note, get a content-address share code, fetch it back by code. -- **[`examples/torrent-dht-channel.livecodescript`](examples/torrent-dht-channel.livecodescript)** - — a minimal single-concept reference for the *mutable* side: a persistent ed25519 - identity, publish signed/updatable values, look up anyone's latest by their key. + they seed — no server anywhere. Includes a signed multi-release feed, a live + color-coded transfers table, shareable channel cards, and an immutable "quick + drop" (pin text, share a 40-char code). The DHT says *where*, BitTorrent moves + *what*. A built-in **"What is this?"** button explains it in plain language. +- **[`examples/torrent-helpers.livecodescript`](examples/torrent-helpers.livecodescript)** + — the reusable **poll dispatcher** (`btStartPolling` / `btStopPolling`) and + formatting sugar (`btFormatBytes`, `btStateName`). `start using` it to drive + engine events as ordinary message-path handlers; the getting-started guide builds + on it. (Both flagship demos are self-contained and run without it.) ## Documentation @@ -227,12 +235,14 @@ sanitizer builds, and the per-platform notes. ## Status -The shim, the LCB binding, the test suite, and four of five platform binaries are -built and gated by CI. Because OpenXTalk has no headless way to compile or run -`.lcb`, runtime behaviour is marked "verified statically; needs an OXT pass" and -confirmed by a human in the IDE — the project does not claim runtime behaviour it -cannot observe. Remaining: the signed macOS universal dylib, and the optional visual -dashboard widget. +The public API spans **75 `bt*` handlers** (ABI v8) — essentially the full +practical libtorrent surface. The shim, the LCB binding, the test suite, and four +of five platform binaries are built and gated by CI; the shim is exercised under +ASan/UBSan against real libtorrent on every change. Because OpenXTalk has no +headless way to compile or run `.lcb`, runtime behaviour is marked "verified +statically; needs an OXT pass" and confirmed by a human in the IDE — the project +does not claim runtime behaviour it cannot observe. Remaining: the signed macOS +universal dylib, and the optional visual dashboard widget. ## License diff --git a/docs/TorrentXT-IMPLEMENTATION-PLAN.md b/docs/TorrentXT-IMPLEMENTATION-PLAN.md index 996e7cd..a8b24dc 100644 --- a/docs/TorrentXT-IMPLEMENTATION-PLAN.md +++ b/docs/TorrentXT-IMPLEMENTATION-PLAN.md @@ -524,9 +524,9 @@ TorrentXT/ │ ├── check-livecodescript.py static gate for .lcb + .livecodescript │ └── package-extension.py refresh the committed code// trees ├── examples/ -│ ├── torrent-helpers.livecodescript the poll dispatcher + sugar -│ ├── torrent-client.livecodescript the flagship multi-torrent client -│ └── torrent-demo.livecodescript minimal add-a-magnet walkthrough +│ ├── torrent-helpers.livecodescript the poll dispatcher + sugar +│ ├── torrent-client.livecodescript the flagship multi-torrent client +│ └── torrent-dht-channels.livecodescript the decentralized DHT channel demo ├── docs/ │ └── architecture.md building.md getting-started.md api-reference.md ├── CMakeLists.txt diff --git a/docs/architecture.md b/docs/architecture.md index 91eb26e..bb3b27e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -24,8 +24,9 @@ can find their footing. | - public bt* handlers: hide every handle, pre-size buffers, | walk records, set the module last-error, never throw to script | - examples/torrent-helpers.livecodescript the poll dispatcher (timer -> btPoll -> messages) - examples/torrent-client.livecodescript the flagship multi-torrent client (self-building UI) + examples/torrent-helpers.livecodescript the poll dispatcher (timer -> btPoll -> messages) + examples/torrent-client.livecodescript the flagship multi-torrent client (self-building UI) + examples/torrent-dht-channels.livecodescript the decentralized DHT + BitTorrent channel demo | your xTalk app writes event handlers (metadataReceived, pieceFinished, ...) ``` @@ -103,8 +104,9 @@ runtime. So the binding uses the proven htmltidy/HIDAPI shape built on the engine `` allocators (foreign handlers that bind by their exact engine name, so they carry no leading underscore): -- **out** (the shim fills it — alert drain, status / peer / DHT / bitfield - snapshots, resume bytes, a created `.torrent`): the binding hands the shim a +- **out** (the shim fills it — alert drain, status / peer / file / tracker / DHT + snapshots, the piece bitfield and per-piece availability, resume bytes, a + created `.torrent`): the binding hands the shim a raw block from `MCMemoryAllocate` as a real `Pointer` plus its capacity; the shim writes into it and returns **bytes-written**, or **`-needed`** when the block was too small. The binding then copies exactly the written bytes back diff --git a/docs/getting-started.md b/docs/getting-started.md index 6cd93f1..1923cc0 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -10,10 +10,10 @@ contract see `docs/api-reference.md`; for the *why* of the design see > **Honesty note.** OXT cannot compile or run `.lcb`/`.livecodescript` headlessly, > so the snippets here are "verified statically; needs an OXT pass." They mirror > the runnable examples — `examples/torrent-client.livecodescript` (the full -> self-building client) plus the smaller `examples/torrent-demo.livecodescript` -> and `examples/torrent-helpers.livecodescript` this guide walks through; when -> something does not behave, trust the running engine and `btLastError()` over -> this page. +> self-building client), `examples/torrent-dht-channels.livecodescript` (the +> decentralized DHT demo), and `examples/torrent-helpers.livecodescript` (the +> poll dispatcher this guide builds on); when something does not behave, trust the +> running engine and `btLastError()` over this page. --- @@ -66,7 +66,8 @@ threads at quit - the documented failure mode (plan section 4.2). `btStopSession **idempotent** and a stale handle is a no-op, so calling it defensively is always safe. Only one session may be live at a time; `btStartSession` refuses a second. -Here is that skeleton, lifted from `examples/torrent-demo.livecodescript`: +Here is that skeleton — the minimal shape every TorrentXT app shares (the +flagship examples wrap the same lifecycle around a richer UI): ``` local sSession diff --git a/examples/torrent-demo.livecodescript b/examples/torrent-demo.livecodescript deleted file mode 100644 index 3e045fc..0000000 --- a/examples/torrent-demo.livecodescript +++ /dev/null @@ -1,130 +0,0 @@ -script "torrentDemo" - --- torrent-demo.livecodescript - the smallest end-to-end TorrentXT app: start a --- session, add a magnet, watch progress via the poll dispatcher, finish, and --- (critically) shut the session down on close. Attach as a stack script over a --- card holding fields "magnet", "savepath", "status" and a button per command. --- --- Pair this with torrent-helpers.livecodescript in the message path --- (`start using stack "torrentHelpers"`), which supplies btStartPolling and the --- semantic dispatch. - -local sSession -- the one live session handle -local sActiveTorrent -- the torrent we are tracking in the dashboard - --- ------------------------------------------------------------ lifecycle -on openStack - -- The native library stays loaded across stack opens and permits only ONE live - -- session (there is no deterministic unload hook - plan section 4.2). So - -- re-opening this stack without a clean close would otherwise hit "a session is - -- already live". ensureSession adopts the session we started last time instead - -- of blindly starting a second one. - ensureSession - if sSession is empty or sSession is 0 then - exit openStack - end if - -- drive events to this card; 250 ms drain is plenty for a UI - btStartPolling sSession, the long id of this card, 250 - -- low-rate dashboard refresh (<= 4 Hz), the cheap way to show progress - send "refreshDashboard" to me in 500 milliseconds -end openStack - --- Acquire THE session: reuse the one we remember if it is still live in the --- loaded library, otherwise start a fresh one. The handle is kept in the stack --- custom property `uSession` so it survives a script-local reset (e.g. an edit + --- recompile that does not fire closeStack). A remembered handle is probed with a --- cheap setting write - btSetBool returns 0 only for a live session - so a stale --- handle from a previous IDE run is harmlessly rejected and we start clean. -command ensureSession - local tRemembered - put the uSession of this stack into tRemembered - if tRemembered is a number and tRemembered > 0 \ - and btSetBool(tRemembered, "enable_dht", true) is 0 then - put tRemembered into sSession - else - btStartSession -- verifies the ABI; returns 0 + sets btLastError on failure - put the result into sSession - if sSession is 0 then - answer "TorrentXT failed to start:" && btLastError() - set the uSession of this stack to empty - exit ensureSession - end if - btSetBool sSession, "enable_dht", true - set the uSession of this stack to sSession - end if - -- a sane default that is cheap to (re)assert whether adopted or fresh - btSetBool sSession, "enable_lsd", true -end ensureSession - -on closeStack - -- MUST shut down explicitly: there is no deterministic extension-unload hook - -- (plan section 4.2). Stop polling first, then pause/flush/destroy the session, - -- and forget the remembered handle so the next open starts clean. - btStopPolling - if sSession is not empty and sSession is not 0 then - btStopSession sSession - end if - put empty into sSession - put empty into sActiveTorrent - set the uSession of this stack to empty -end closeStack - --- ------------------------------------------------------------ commands -command addMagnet - local tHandle - if sSession is 0 or sSession is empty then - answer "No session." - exit addMagnet - end if - btAddMagnet sSession, field "magnet", field "savepath" - put the result into tHandle - if tHandle is 0 then - answer "Add failed:" && btLastError() - else - put tHandle into sActiveTorrent - put "Added; fetching metadata..." into field "status" - end if -end addMagnet - -command pauseActive - if sActiveTorrent is not empty then - btPause sActiveTorrent - end if -end pauseActive - -command resumeActive - if sActiveTorrent is not empty then - btResume sActiveTorrent - end if -end resumeActive - --- ------------------------------------------------------------ event handlers --- (these names are the semantic messages the poll dispatcher sends) -on metadataReceived pEvent - put "Metadata:" && pEvent["torrentName"] into field "status" -end metadataReceived - -on torrentFinished pEvent - put "Finished torrent" && pEvent["torrent"] into field "status" -end torrentFinished - -on torrentError pEvent - put "Error:" && pEvent["errorMessage"] into field "status" -end torrentError - --- ------------------------------------------------------------ dashboard --- The right way to show progress: a low-rate poll of the one-call status --- snapshot, NOT a redraw per pieceFinished event (single-thread playbook). -command refreshDashboard - local tStatus, tPct, tLine - if sActiveTorrent is not empty then - put btTorrentStatus(sActiveTorrent) into tStatus - put (the round of (tStatus["progress"] * 1000) / 10) into tPct - put tStatus["name"] && "-" && btStateName(tStatus["state"]) into tLine - put tLine & " - " & tPct & "% - down" && btFormatBytes(tStatus["downloadRate"]) & "/s" \ - into field "status" - end if - if sSession is not empty and sSession is not 0 then - send "refreshDashboard" to me in 500 milliseconds - end if -end refreshDashboard diff --git a/examples/torrent-dht-channel.livecodescript b/examples/torrent-dht-channel.livecodescript deleted file mode 100644 index c273bca..0000000 --- a/examples/torrent-dht-channel.livecodescript +++ /dev/null @@ -1,316 +0,0 @@ -script "dhtChannelDemo" - --- torrent-dht-channel.livecodescript - a self-building TorrentXT demo of the --- BEP44 DHT MUTABLE items: publish signed, updatable values under an identity --- you own, and look up anyone's latest value by their public key. See also --- torrent-dht-note.livecodescript (immutable / content-addressed items). --- --- HOW TO USE: paste into a one-card stack's STACK script, then close+reopen. --- Needs only the loaded org.openxtalk.library.torrent library (v3+). --- --- WHAT IT SHOWS: "a mailbox the world can read but only you can write." On first --- run it generates an ed25519 identity and remembers it across launches (the seed --- is stored in a stack custom property - fine for a demo, not secure storage). --- You publish a small value (<= 1000 bytes) under your public key; anyone who --- knows that key fetches your LATEST value, with its version (seq). Only your --- private key can update it. A magnet link fits easily in 1000 bytes, so this is --- the "publish a pointer to my current content" / rendezvous primitive: put a --- magnet here, and readers fetch your key -> get the magnet -> btAddMagnet it. --- --- NB: only ONE TorrentXT session can be live per OXT process, so to see one --- machine publish and ANOTHER read, run this stack on two machines and exchange --- public keys. On a single machine you can publish then Fetch-mine to round-trip. - --- ---- stack-local state ------------------------------------------------------- -local sSession -- the live session handle (0/empty = none) -local sPolling -- "true" while the event poll is armed -local sPub -- our public key (hex) - shareable -local sSec -- our secret key (hex) - kept in script only, never shown - --- ---- lifecycle --------------------------------------------------------------- -on openStack - dcBuild - dcStart -end openStack - -on closeStack - dcStop -end closeStack - -on mouseUp - switch the short name of the target - case "dcPublish" - dcPublish - break - case "dcFetch" - dcFetch - break - case "dcMine" - dcFetchMine - break - case "dcCopy" - dcCopyKey - break - case "dcNew" - dcNewIdentity - break - default - pass mouseUp - end switch -end mouseUp - --- ---- self-building UI (idempotent) ------------------------------------------- -command dcBuild - if there is a field "dcLog" then - exit dcBuild - end if - lock screen - set the width of this stack to 600 - set the height of this stack to 560 - set the backgroundColor of this card to "237,239,243" - set the title of this stack to "TorrentXT - DHT Channel" - dcMakeLabel "dcTitle", " TorrentXT - DHT Mutable Channel", "0,0,600,30", 15 - set the opaque of field "dcTitle" to true - set the backgroundColor of field "dcTitle" to "44,90,160" - set the foregroundColor of field "dcTitle" to "255,255,255" - set the textStyle of field "dcTitle" to "bold" - dcMakeLabel "dcIntro", "Publish signed, updatable values under an ed25519 key you own. Anyone with your public key reads your latest value; only you can change it.", "16,38,584,74", 10 - dcMakeLabel "dcMyKeyLabel", "Your identity (public key) - share it so others can read your channel:", "16,80,584,98", 11 - dcMakeField "dcMyKey", "16,100,420,124", true - dcMakeButton "dcCopy", "Copy", "428,100,506,124" - dcMakeButton "dcNew", "New", "510,100,584,124" - dcMakeLabel "dcValueLabel", "Publish a value (<= 1000 bytes; a magnet link fits):", "16,132,584,150", 11 - dcMakeField "dcValue", "16,152,490,204", false - dcMakeButton "dcPublish", "Publish", "498,152,584,178" - dcMakeLabel "dcLookupLabel", "Look up a public key:", "16,214,584,232", 11 - dcMakeField "dcLookup", "16,234,400,258", false - dcMakeButton "dcFetch", "Fetch latest", "408,234,496,258" - dcMakeButton "dcMine", "Fetch mine", "500,234,584,258" - dcMakeLabel "dcResultLabel", "Latest value:", "16,270,584,288", 11 - dcMakeField "dcResult", "16,290,584,348", true - dcMakeLabel "dcResultMeta", "", "16,352,584,370", 10 - dcMakeLabel "dcLogLabel", "Activity:", "16,376,584,394", 11 - dcMakeList "dcLog", "16,396,584,548" - set the textFont of field "dcMyKey" to "Courier" - set the textFont of field "dcLookup" to "Courier" - -- a 64-hex key needs a small fixed-width size to sit on one line - set the textSize of field "dcMyKey" to 9 - set the textSize of field "dcLookup" to 9 - unlock screen -end dcBuild - -command dcMakeLabel pName, pText, pRect, pSize - if there is no field pName then - create field - set the name of the last field to pName - end if - set the rect of field pName to pRect - set the lockText of field pName to true - set the borderWidth of field pName to 0 - set the opaque of field pName to false - set the textSize of field pName to pSize - put pText into field pName -end dcMakeLabel - -command dcMakeField pName, pRect, pLocked - if there is no field pName then - create field - set the name of the last field to pName - end if - set the rect of field pName to pRect - set the lockText of field pName to pLocked - set the traversalOn of field pName to (not pLocked) -end dcMakeField - -command dcMakeButton pName, pLabel, pRect - if there is no button pName then - create button - set the name of the last button to pName - end if - set the rect of button pName to pRect - set the label of button pName to pLabel -end dcMakeButton - -command dcMakeList pName, pRect - if there is no field pName then - create field - set the name of the last field to pName - end if - set the rect of field pName to pRect - set the lockText of field pName to true - set the vScrollbar of field pName to true -end dcMakeList - --- ---- session, identity, event poll ------------------------------------------- -command dcStart - btStartSession - put the result into sSession - if sSession is 0 or sSession is empty then - dcLog "Could not start a session (is another TorrentXT stack open?):" && btLastError() - put 0 into sSession - exit dcStart - end if - btSetBool sSession, "enable_dht", true - btDhtAddBootstrap sSession, "router.bittorrent.com", 6881 - btDhtAddBootstrap sSession, "router.utorrent.com", 6881 - btDhtAddBootstrap sSession, "dht.transmissionbt.com", 6881 - dcEnsureIdentity - dcLog "Session ready. The DHT is bootstrapping - give it a few seconds before publishing or fetching." - put "true" into sPolling - send "dcPollOnce" to me in 250 milliseconds -end dcStart - --- Re-derive our keypair from a remembered seed, or make a new identity the first --- time and remember its seed for next launch. -command dcEnsureIdentity - local tKey, tSeed - put the uDhtSeed of this stack into tSeed - if tSeed is not empty then - put btDhtKeypair(tSeed) into tKey - else - put btDhtKeypair("") into tKey - set the uDhtSeed of this stack to tKey["seed"] - end if - put tKey["publicKey"] into sPub - put tKey["secretKey"] into sSec - set the text of field "dcMyKey" to sPub -end dcEnsureIdentity - -command dcStop - put "false" into sPolling - if sSession is not empty and sSession is not 0 then - btStopSession sSession - end if - put empty into sSession -end dcStop - -command dcPollOnce - local tEvents, tEvent - if sPolling is not "true" then - exit dcPollOnce - end if - if sSession is 0 or sSession is empty then - exit dcPollOnce - end if - put btPoll(sSession) into tEvents - repeat for each element tEvent in tEvents - dcHandleEvent tEvent - end repeat - send "dcPollOnce" to me in 250 milliseconds -end dcPollOnce - --- ---- actions ----------------------------------------------------------------- -command dcPublish - local tData, tR - if sSession is 0 then - dcLog "No session." - exit dcPublish - end if - put textEncode(field "dcValue", "UTF-8") into tData - if the number of bytes in tData is 0 then - dcLog "Type a value to publish first." - exit dcPublish - end if - if the number of bytes in tData > 1000 then - dcLog "Too big:" && (the number of bytes in tData) && "bytes (max 1000)." - exit dcPublish - end if - -- signed and sequence-bumped in the native layer; confirms via a dhtPut event - put btDhtPutMutable(sSession, sPub, sSec, "", tData) into tR - if tR is not 0 then - dcLog "Publish failed:" && btLastError() - exit dcPublish - end if - dcLog "Publishing" && (the number of bytes in tData) && "bytes under your key... (confirms as an event)" -end dcPublish - -command dcFetch - local tKey, tR - if sSession is 0 then - dcLog "No session." - exit dcFetch - end if - put dcTrim(field "dcLookup") into tKey - if the number of chars of tKey is not 64 then - dcLog "Paste a 64-character public key into the lookup field first." - exit dcFetch - end if - put btDhtGetMutable(sSession, tKey, "") into tR - if tR is not 0 then - dcLog "Fetch failed:" && btLastError() - exit dcFetch - end if - dcLog "Looking up the latest value under" && (char 1 to 12 of tKey) & "... (returns as an event)" -end dcFetch - -command dcFetchMine - set the text of field "dcLookup" to sPub - dcFetch -end dcFetchMine - -command dcCopyKey - if sPub is empty then - exit dcCopyKey - end if - set the clipboardData["text"] to sPub - dcLog "Copied your public key to the clipboard - share it so others can read your channel." -end dcCopyKey - -command dcNewIdentity - local tKey - answer "Generate a NEW identity? Values published under your current key become unreachable to anyone using the old key." with "Cancel" or "New identity" - if it is not "New identity" then - exit dcNewIdentity - end if - put btDhtKeypair("") into tKey - set the uDhtSeed of this stack to tKey["seed"] - put tKey["publicKey"] into sPub - put tKey["secretKey"] into sSec - set the text of field "dcMyKey" to sPub - dcLog "New identity:" && sPub -end dcNewIdentity - --- ---- events (drained by the poll loop) --------------------------------------- -command dcHandleEvent pEvent - local tMeta - switch pEvent["name"] - case "dhtMutableItem" - set the text of field "dcResult" to textDecode(pEvent["value"], "UTF-8") - put "version (seq):" && pEvent["seq"] into tMeta - if pEvent["authoritative"] is "1" then - put tMeta && "- authoritative" into tMeta - end if - set the text of field "dcResultMeta" to tMeta - dcLog "Got latest (seq" && pEvent["seq"] & ") under" && (char 1 to 12 of pEvent["publicKey"]) & "." - break - case "dhtPut" - dcLog "Published to" && pEvent["numSuccess"] && "DHT node(s)." - break - default - break - end switch -end dcHandleEvent - --- ---- helpers ----------------------------------------------------------------- -command dcLog pMsg - if there is no field "dcLog" then - exit dcLog - end if - put the time & " " & pMsg & return after field "dcLog" - if the number of lines of field "dcLog" > 200 then - delete line 1 to (the number of lines of field "dcLog" - 200) of field "dcLog" - end if - set the vScroll of field "dcLog" to the formattedHeight of field "dcLog" -end dcLog - -function dcTrim pText - local tT - put pText into tT - repeat while tT begins with space or tT begins with tab or tT begins with return - delete char 1 of tT - end repeat - repeat while tT ends with space or tT ends with tab or tT ends with return - delete the last char of tT - end repeat - return tT -end dcTrim diff --git a/examples/torrent-dht-note.livecodescript b/examples/torrent-dht-note.livecodescript deleted file mode 100644 index 12a7ac4..0000000 --- a/examples/torrent-dht-note.livecodescript +++ /dev/null @@ -1,265 +0,0 @@ -script "dhtNoteDemo" - --- torrent-dht-note.livecodescript - a self-building TorrentXT demo of the BEP44 --- DHT as a CONTENT-ADDRESSED store (immutable items). One of the small DHT --- key-value demos; see also torrent-dht-channel.livecodescript (mutable items). --- --- HOW TO USE: open OXT, make a stack with one card, paste THIS into the STACK --- script (Object > Stack Script), then close+reopen the stack. It builds its own --- UI and starts a TorrentXT session. Needs only the loaded --- org.openxtalk.library.torrent library (v3+, which has the btDht* item calls). --- --- WHAT IT SHOWS: "pin a note, share the code." Pin a short note (<= 1000 bytes) --- to the DHT and get back a 40-character target hash - its content address. --- Anyone (here, or on another machine) who has that code can fetch the exact --- bytes back. The value cannot be changed; the code only ever matches that data. --- The DHT is asynchronous and best-effort, so the value comes back as an EVENT a --- few seconds later (and only once the DHT has peers), drained by the poll loop. - --- ---- stack-local state ------------------------------------------------------- -local sSession -- the live session handle (0/empty = none) -local sPolling -- "true" while the event poll is armed - --- ---- lifecycle --------------------------------------------------------------- -on openStack - dnBuild - dnStart -end openStack - -on closeStack - dnStop -end closeStack - -on mouseUp - switch the short name of the target - case "dnPin" - dnPin - break - case "dnCopy" - dnCopyCode - break - case "dnFetch" - dnFetch - break - default - pass mouseUp - end switch -end mouseUp - --- ---- self-building UI (idempotent) ------------------------------------------- -command dnBuild - if there is a field "dnLog" then - exit dnBuild - end if - lock screen - set the width of this stack to 600 - set the height of this stack to 540 - set the backgroundColor of this card to "237,239,243" - set the title of this stack to "TorrentXT - DHT Note" - dnMakeLabel "dnTitle", " TorrentXT - DHT Immutable Note", "0,0,600,30", 15 - set the opaque of field "dnTitle" to true - set the backgroundColor of field "dnTitle" to "44,90,160" - set the foregroundColor of field "dnTitle" to "255,255,255" - set the textStyle of field "dnTitle" to "bold" - dnMakeLabel "dnIntro", "Pin a short note to the DHT and get a share code (its content address). Anyone with the code can fetch the exact bytes back - no server.", "16,38,584,74", 10 - dnMakeLabel "dnNoteLabel", "Note to pin (<= 1000 bytes):", "16,80,584,98", 11 - dnMakeField "dnNote", "16,100,584,178", false - dnMakeButton "dnPin", "Pin to DHT", "16,186,150,212" - dnMakeLabel "dnBytes", "0 bytes", "160,190,584,210", 10 - dnMakeLabel "dnCodeLabel", "Share code (give this to anyone to fetch the note):", "16,222,584,240", 11 - dnMakeField "dnCode", "16,242,470,266", true - dnMakeButton "dnCopy", "Copy code", "478,242,584,266" - dnMakeLabel "dnFetchLabel", "Fetch a code:", "16,278,584,296", 11 - dnMakeField "dnTarget", "16,298,470,322", false - dnMakeButton "dnFetch", "Fetch", "478,298,584,322" - dnMakeLabel "dnResultLabel", "Fetched value:", "16,334,584,352", 11 - dnMakeField "dnResult", "16,354,584,412", true - dnMakeLabel "dnLogLabel", "Activity:", "16,420,584,438", 11 - dnMakeList "dnLog", "16,440,584,528" - set the textFont of field "dnCode" to "Courier" - set the textFont of field "dnTarget" to "Courier" - unlock screen -end dnBuild - -command dnMakeLabel pName, pText, pRect, pSize - if there is no field pName then - create field - set the name of the last field to pName - end if - set the rect of field pName to pRect - set the lockText of field pName to true - set the borderWidth of field pName to 0 - set the opaque of field pName to false - set the textSize of field pName to pSize - put pText into field pName -end dnMakeLabel - -command dnMakeField pName, pRect, pLocked - if there is no field pName then - create field - set the name of the last field to pName - end if - set the rect of field pName to pRect - set the lockText of field pName to pLocked - set the traversalOn of field pName to (not pLocked) -end dnMakeField - -command dnMakeButton pName, pLabel, pRect - if there is no button pName then - create button - set the name of the last button to pName - end if - set the rect of button pName to pRect - set the label of button pName to pLabel -end dnMakeButton - -command dnMakeList pName, pRect - if there is no field pName then - create field - set the name of the last field to pName - end if - set the rect of field pName to pRect - set the lockText of field pName to true - set the vScrollbar of field pName to true -end dnMakeList - --- ---- session + event poll ---------------------------------------------------- -command dnStart - btStartSession - put the result into sSession - if sSession is 0 or sSession is empty then - dnLog "Could not start a session (is another TorrentXT stack open?):" && btLastError() - put 0 into sSession - exit dnStart - end if - btSetBool sSession, "enable_dht", true - -- seed the DHT so it bootstraps reliably on first run - btDhtAddBootstrap sSession, "router.bittorrent.com", 6881 - btDhtAddBootstrap sSession, "router.utorrent.com", 6881 - btDhtAddBootstrap sSession, "dht.transmissionbt.com", 6881 - dnLog "Session ready. The DHT is bootstrapping - give it a few seconds of peers before a Fetch will find anything." - put "true" into sPolling - send "dnPollOnce" to me in 250 milliseconds -end dnStart - -command dnStop - put "false" into sPolling - if sSession is not empty and sSession is not 0 then - btStopSession sSession - end if - put empty into sSession -end dnStop - -command dnPollOnce - local tEvents, tEvent, tBytes - if sPolling is not "true" then - exit dnPollOnce - end if - if sSession is 0 or sSession is empty then - exit dnPollOnce - end if - put btPoll(sSession) into tEvents - repeat for each element tEvent in tEvents - dnHandleEvent tEvent - end repeat - -- keep the live byte-count fresh, but only repaint it when it changes - put (the number of bytes in textEncode(field "dnNote", "UTF-8")) && "bytes" into tBytes - if the text of field "dnBytes" is not tBytes then - set the text of field "dnBytes" to tBytes - end if - send "dnPollOnce" to me in 250 milliseconds -end dnPollOnce - --- ---- actions ----------------------------------------------------------------- -command dnPin - local tData, tTarget - if sSession is 0 then - dnLog "No session." - exit dnPin - end if - put textEncode(field "dnNote", "UTF-8") into tData - if the number of bytes in tData is 0 then - dnLog "Type a note first." - exit dnPin - end if - if the number of bytes in tData > 1000 then - dnLog "Too big:" && (the number of bytes in tData) && "bytes (max 1000)." - exit dnPin - end if - -- the target hash comes back immediately; the store itself confirms via event - put btDhtPutImmutable(sSession, tData) into tTarget - if tTarget is empty then - dnLog "Pin failed:" && btLastError() - exit dnPin - end if - put tTarget into field "dnCode" - put tTarget into field "dnTarget" -- pre-fill Fetch so you can round-trip it - dnLog "Pinned" && (the number of bytes in tData) && "bytes. Share code:" && tTarget -end dnPin - -command dnCopyCode - if field "dnCode" is empty then - dnLog "Pin a note first." - exit dnCopyCode - end if - set the clipboardData["text"] to field "dnCode" - dnLog "Copied the share code to the clipboard." -end dnCopyCode - -command dnFetch - local tTarget, tR - if sSession is 0 then - dnLog "No session." - exit dnFetch - end if - put dnTrim(field "dnTarget") into tTarget - if the number of chars of tTarget is not 40 then - dnLog "Paste a 40-character share code into Fetch first." - exit dnFetch - end if - put btDhtGetImmutable(sSession, tTarget) into tR - if tR is not 0 then - dnLog "Fetch failed:" && btLastError() - exit dnFetch - end if - dnLog "Looking up" && tTarget & "... (the value returns as an event)" -end dnFetch - --- ---- events (drained by the poll loop) --------------------------------------- -command dnHandleEvent pEvent - switch pEvent["name"] - case "dhtImmutableItem" - set the text of field "dnResult" to textDecode(pEvent["value"], "UTF-8") - dnLog "Fetched the value for" && pEvent["target"] & "." - break - case "dhtPut" - dnLog "Stored on" && pEvent["numSuccess"] && "DHT node(s)." - break - default - break - end switch -end dnHandleEvent - --- ---- helpers ----------------------------------------------------------------- -command dnLog pMsg - if there is no field "dnLog" then - exit dnLog - end if - put the time & " " & pMsg & return after field "dnLog" - if the number of lines of field "dnLog" > 200 then - delete line 1 to (the number of lines of field "dnLog" - 200) of field "dnLog" - end if - set the vScroll of field "dnLog" to the formattedHeight of field "dnLog" -end dnLog - -function dnTrim pText - local tT - put pText into tT - repeat while tT begins with space or tT begins with tab or tT begins with return - delete char 1 of tT - end repeat - repeat while tT ends with space or tT ends with tab or tT ends with return - delete the last char of tT - end repeat - return tT -end dnTrim diff --git a/tools/package-extension.py b/tools/package-extension.py index cf5f581..de733af 100644 --- a/tools/package-extension.py +++ b/tools/package-extension.py @@ -187,12 +187,12 @@ def stage(rel_src, rel_dst): dst = os.path.join(staging, rel_dst) actions.append((src, dst)) - # The LCB binding and the script sugar (present from Phase 1 on). + # The LCB binding, the poll-dispatcher sugar, and the two flagship demos. stage(os.path.join("src", "torrent.lcb"), "torrent.lcb") - stage(os.path.join("examples", "torrent-helpers.livecodescript"), - os.path.join("examples", "torrent-helpers.livecodescript")) - stage(os.path.join("examples", "torrent-demo.livecodescript"), - os.path.join("examples", "torrent-demo.livecodescript")) + for ex in ("torrent-helpers.livecodescript", + "torrent-client.livecodescript", + "torrent-dht-channels.livecodescript"): + stage(os.path.join("examples", ex), os.path.join("examples", ex)) # Every committed per-platform library currently in the tree. if os.path.isdir(CODE_ROOT):