ElixirTorrent

BitTorrent client engine for Elixir/OTP — embed downloads in your own app with a small, stable public API.

About

This is a fully functional BitTorrent client that actually downloads torrents — not a stub or protocol sketch. It started as a course project for Functional Programming with Elixir at Sofia University. After the course ended, development continued in spare time until it was ready to publish on Hex.

Hexhex.pm/packages/elixir_torrent
Docshexdocs.pm/elixir_torrent
Changeloghexdocs.pm/elixir_torrent/changelog.html
Sourcegithub.com/daniboybye/ElixirTorrent

ElixirTorrent Web (desktop app)

Need a full client, not just the library? ElixirTorrent Web is the official Phoenix LiveView UI for this engine, shipped as a native macOS desktop app.

The sections below cover the engine API for Elixir developers embedding BitTorrent in their own apps.

Installation

Add elixir_torrent to your list of dependencies in mix.exs:

def deps do
[
{:elixir_torrent, "~> 0.2.0"}
]
end

Then fetch and start the application:

mix deps.get
# In your Application.start/2 or before first use:
Application.ensure_all_started(:elixir_torrent)

Requires Elixir 1.20+.

Quick start

Application.ensure_all_started(:elixir_torrent)
{:ok, pid} = ElixirTorrent.download("/path/to/file.torrent")
[hash] = ElixirTorrent.list()
{:ok, stats} =
ElixirTorrent.stats(pid, [:name, :speed, :downloaded, :bytes_size])
# stats.name, stats.speed.download, stats.speed.upload, …
files = ElixirTorrent.list_files(hash)
# Each entry has :path, :progress, :complete?, etc.

Poll stats/2 while the download runs. When you are done monitoring, call stop_and_serialize/1 (see below) or remove/2 to drop the torrent from the active session.

Session persistence

The engine can save and restore download progress across process restarts.

On disk:{File.cwd!()}/.elixir_torrent/state/{hex_info_hash}.term

Each file stores the bitfield, byte counters, and peer status. When you call download/1 with a .torrent that was previously saved, the engine loads the session, verifies pieces against disk, and resumes from the saved bitfield.

Persist before exit:

ElixirTorrent.stop_and_serialize(hash)
# or, for every active torrent:
ElixirTorrent.stop_all_and_serialize()

Remove without keeping progress:

ElixirTorrent.remove(hash)
# also delete downloaded files:
ElixirTorrent.remove(hash, delete_data: true)

remove/2 deletes the session file. stop_and_serialize/1 writes a fresh snapshot and keeps the torrent removable on the next boot via the same .torrent path.

Graceful shutdown

stop_and_serialize/1 runs, in order:

  1. Stop active piece downloads
  2. Disconnect all peers (BEP 3 cancel / not interested / choke, then TCP close)
  3. Send tracker announce with event=stopped
  4. Write session state to .elixir_torrent/state/
  5. Stop the torrent OTP process

Use this when your application shuts down and you want downloads to resume later. stop_all_and_serialize/0 applies the same steps to every running torrent.

Public API

Full reference: hexdocs.pm/elixir_torrent/ElixirTorrent.html

FunctionDescription
download/1Start a download from a local .torrent path; returns {:ok, pid}
stats/2Runtime stats map (:name, :speed, :downloaded, :bytes_size, …)
list/0Info hashes for all active torrent processes
list_files/1Per-file paths and download progress
stop_and_serialize/1Graceful stop + persist session
stop_all_and_serialize/0Graceful stop + persist for every torrent
remove/2Stop and drop from session; optional delete_data: true
get/2Low-level field access (prefer stats/2)
version/0Client peer ID prefix (ET0-2-0, BEP 20)

Supported BEPs

BEPTopic
BEP 3BitTorrent protocol
BEP 4Known number allocations
BEP 6Fast extension
BEP 7IPv6 tracker extension
BEP 12Multitracker metadata
BEP 15UDP tracker protocol
BEP 20Peer ID conventions
BEP 23Compact peer lists
BEP 24Tracker returns external IP
BEP 31Failure retry extension

CLI (escript)

The package builds an escript for ad-hoc testing:

mix escript.build
./elixir_torrent
# then type: download /path/to/file.torrent

For production use, call the API from your OTP application instead.