Packages

torrent

Create and read .torrent files and magnet links, and download or seed files as a BitTorrent client.

import "std:torrent"

BitTorrent shares files between many peers instead of one server. A .torrent file (or a magnet link) describes the files and fingerprints each piece, so a downloader can check what it gets. std:torrent can create and read these descriptions, and act as a client that downloads and seeds.

Making and reading metainfo works offline and runs below. Downloading needs peers on the internet, so those examples are norun.

Describe a file#

torrent.fromPath(path, options) reads a file (or a folder) and builds its metainfo: its size, the pieces and their hashes. Options set the pieceLength and trackers:

main.osl
import "std:torrent"
import "std:fs"

fs.writeFile("notes.txt", "hello torrent " * 1000).unwrap()

auto t = torrent.fromPath("notes.txt", {
  pieceLength: 16384,
  trackers: ["udp://tracker.example:80"]
}).unwrap()
log t.name() t.size()
log t.pieceLength() t.pieceCount()
log t.files()
log t.trackers()
Click the code to edit it

Change the text to * 5000 and run again: the file gets more pieces.

The info hash#

The info hash is the torrent's identity: 40 hex digits computed from the content and its description. The same file with the same settings always gets the same one, and any change gets a different one:

main.osl
import "std:torrent"
import "std:fs"

fs.writeFile("notes.txt", "hello torrent " * 1000).unwrap()
auto t = torrent.fromPath("notes.txt", {pieceLength: 16384}).unwrap()

log t.infoHash()
log t.infoHash().len

fs.writeFile("notes.txt", "hello torrent " * 1001).unwrap()
auto changed = torrent.fromPath("notes.txt", {pieceLength: 16384}).unwrap()
log changed.infoHash() == t.infoHash()
Click the code to edit it

A magnet link carries the info hash, the name and the trackers in one line of text, which is easy to paste and share:

main.osl
import "std:torrent"
import "std:fs"

fs.writeFile("notes.txt", "hello torrent " * 1000).unwrap()
auto t = torrent.fromPath("notes.txt", {
  pieceLength: 16384,
  trackers: ["udp://tracker.example:80"]
}).unwrap()

string link = t.magnetURI()
log link.startsWith("magnet:?xt=urn:btih:" ++ t.infoHash())
log link.contains("dn=notes.txt")
Click the code to edit it

Save and load a .torrent file#

save(path) writes the .torrent file, decodeFile(path) reads one, and encode() and decode(bytes) do the same in memory. A bad file is a ParseError:

main.osl
import "std:torrent"
import "std:fs"

fs.writeFile("notes.txt", "hello torrent " * 1000).unwrap()
auto t = torrent.fromPath("notes.txt", {pieceLength: 16384}).unwrap()
t.save("notes.torrent").unwrap()

auto back = torrent.decodeFile("notes.torrent").unwrap()
log back.name() back.infoHash() == t.infoHash()

log torrent.decode("this is not bencode").unwrapErr().kind
log torrent.decodeFile("missing.torrent").unwrapErr().kind

fs.remove("notes.torrent", {force: true}).unwrap()
Click the code to edit it

Trackers#

addTracker and removeTracker change the torrent in place and return whether anything changed. The info hash stays the same, since trackers aren't part of the content:

main.osl
import "std:torrent"
import "std:fs"

fs.writeFile("notes.txt", "hello torrent " * 1000).unwrap()
auto t = torrent.fromPath("notes.txt", {pieceLength: 16384}).unwrap()
string before = t.infoHash()

log t.addTracker("udp://one.example:80")
log t.addTracker("udp://one.example:80")
log t.trackers()
log t.removeTracker("udp://one.example:80")
log t.infoHash() == before
Click the code to edit it
Quick check

You add a tracker to a torrent. What happens to its info hash?

Download#

torrent.downloadMagnet(uri, dir) fetches a torrent from its magnet link and waits for it. For a long download or several at once, make a client and watch progress:

main.osl
import "std:torrent"
import "std:thread"

auto client = torrent.newClient({dir: "downloads"}).unwrap()
auto download = client.addMagnet("magnet:?xt=urn:btih:...").unwrap()

download.waitForInfo().unwrap()
log "downloading" download.name()

while !download.isComplete() (
  auto p = download.progress()
  log (p.fraction * 100) ++ "%" p.peers "peers"
  thread.sleep(1000)
)
download.wait().unwrap()
client.close()
Click the code to edit it

Seed#

Seeding uploads data you already have. Give a torrent the folder that holds its files:

main.osl
import "std:torrent"

auto t = torrent.decodeFile("notes.torrent").unwrap()
auto seeding = t.seedFrom(".").unwrap()
log "seeding on" seeding.client().port()
seeding.wait().unwrap()
Click the code to edit it

Only download and share what you have the right to.

Your turn

Describe a file

Create data.txt holding the text abc repeated 100 times, build a torrent with a pieceLength of 16384, and print its name, its size and its piece count.

main.osl
Ctrl Enter runs
Expected output
data.txt 300 1

API reference#

96 members, generated from the std:torrent source of OSL 9.3.0.

Functions

#torrent.addMetadata(any key, any value)

Deprecated. Deprecated: keep extra data in your own object instead.

addMetadata stores a key and value on the package's empty torrent.

#torrent.addTracker(any tracker) boolean

Deprecated. Deprecated: use torrent.addTracker(url) on a torrent instead.

addTracker adds a tracker to the package's empty torrent.

#torrent.buildTorrent() string

Deprecated. Deprecated: use torrent.encode() on a torrent instead.

buildTorrent encodes the package's empty torrent.

#torrent.calculateInfoHash() string

Deprecated. Deprecated: use torrent.infoHash() on a torrent instead.

calculateInfoHash returns the package's empty torrent's info hash.

#torrent.clone() *torrent.Torrent

Deprecated. Deprecated: use torrent.clone() on a torrent instead.

clone copies the package's empty torrent.

#torrent.create(any name, array files, any pieceLength) *torrent.Torrent

Deprecated. Deprecated: use torrent.fromPath(path, options?) instead, which fails with a typed error instead of skipping files.

create(name, files, pieceLength) builds a torrent from local file paths, skipping missing ones, with pieceLength defaulting to 262144.

#torrent.createFromDirectory(any dirPath) *torrent.Torrent

Deprecated. Deprecated: use torrent.fromPath(path) instead.

createFromDirectory builds a torrent from every regular file under a directory, with 512 KiB pieces.

#torrent.decode(byte[] data) result<*torrent.Torrent, error>

decode reads metainfo from the bytes of a .torrent file. The info hash is computed from the bytes as given, so it matches what other clients compute. Fails with ParseError for malformed or non-canonical bencoding, missing or invalid fields, piece hashes that don't match the file sizes, and file paths that would escape the torrent's directory.

#torrent.decodeFile(string path) result<*torrent.Torrent, error>

decodeFile reads the .torrent file at path, like decode. Fails with NotFoundError or PermissionError when it can't be read, and ParseError.

#torrent.download(any torrentPath, any outputPath) result<unit, error>

Deprecated. Deprecated: use torrent.downloadTo(dir, options?) on a torrent, or torrent.downloadMagnet(uri, dir, options?), instead.

download fails with UnsupportedError.

#torrent.downloadMagnet(string uri, string dir, object? options) result<{files: {length: int, path: string}[], infoHash: string, name: string, path: string, size: int}, error>

downloadMagnet downloads the torrent a magnet URI names into dir, with the options and result of Torrent.downloadTo. Fails with ParseError, TimeoutError, IOError, PermissionError or NetworkError.

#torrent.exportInfo(any path) boolean

Deprecated. Deprecated: write the values you need with std:json and std:fs instead.

exportInfo writes the package's empty torrent's info as JSON.

#torrent.fromPath(string path, object? options) result<*torrent.Torrent, error>

fromPath builds the metainfo for the file or directory at path, reading it all to hash its pieces. A directory becomes a multi-file torrent of every regular file in it, sorted by path; symlinks and other special files are skipped. Options: name (default the base name of path), pieceLength (bytes, a power of two from 16384 to 67108864; by default chosen from the total size), trackers (an array of announce URLs), comment, createdBy (default "OSL") and private (boolean, default false). Fails with NotFoundError or PermissionError when path can't be read and ValueError when it has no files. Throws ValueError for invalid options.

#torrent.generatePeerID() string

Deprecated. Deprecated: torrent.newClient makes its own peer ID.

generatePeerID returns "OSL-" and 12 random hex digits.

#torrent.getAllMetadata() object

Deprecated. Deprecated: keep extra data in your own object instead.

getAllMetadata returns the values stored on the package's empty torrent.

#torrent.getFileCount() int

Deprecated. Deprecated: use torrent.files().len on a torrent instead.

getFileCount returns the package's empty torrent's file count.

#torrent.getFiles() object[]

Deprecated. Deprecated: use torrent.files() on a torrent instead.

getFiles always returns [].

#torrent.getInfo() object

Deprecated. Deprecated: use name(), pieceLength() and files() on a torrent instead.

getInfo returns the package's empty torrent's info.

#torrent.getMagnetURI() string

Deprecated. Deprecated: use torrent.magnetURI() on a torrent instead.

getMagnetURI returns the package's empty torrent's magnet link.

#torrent.getMetadata(any key) string

Deprecated. Deprecated: keep extra data in your own object instead.

getMetadata returns a value stored on the package's empty torrent.

#torrent.getPieceCount() int

Deprecated. Deprecated: use torrent.pieceCount() on a torrent instead.

getPieceCount always returns 0.

#torrent.getPieceHashes() string[]

Deprecated. Deprecated: use torrent.pieces() on a torrent instead.

getPieceHashes always returns [].

#torrent.getTotalSize() int64

Deprecated. Deprecated: use torrent.size() on a torrent instead.

getTotalSize returns the package's empty torrent's size.

#torrent.getTrackers() array

Deprecated. Deprecated: use torrent.trackers() on a torrent instead.

getTrackers returns the package's empty torrent's trackers.

#torrent.merge(*torrent.Torrent otherTorrent) *torrent.Torrent

Deprecated. Deprecated: build a torrent of a directory with torrent.fromPath instead.

merge returns a copy of otherTorrent's files as a new torrent.

#torrent.newClient(object? options) result<*torrent.Client, error>

newClient starts a BitTorrent client that listens for peers. Options: dir (where downloads are stored, default the current directory; created if missing), listenPort (the TCP and uTP port, default 0 for a random free one; see Client.port), seed (boolean, default false: keep uploading torrents that are complete), dht (boolean, default true: find peers through the public DHT), upload (boolean, default true; false never sends data to peers), uploadRateLimit and downloadRateLimit (bytes per second, default 0 for no limit) and portForwarding (boolean, default false: ask the router to forward listenPort over UPnP). Fails with NetworkError when the port can't be listened on, and PermissionError or IOError when dir can't be created. Throws ValueError for invalid options.

#torrent.parse(any torrentData) *torrent.Torrent

Deprecated. Deprecated: use torrent.decode(data) instead, which fails with ParseError.

parse reads a torrent from bencoded text, returning an empty torrent when it is invalid.

#torrent.removeTracker(any tracker) boolean

Deprecated. Deprecated: use torrent.removeTracker(url) on a torrent instead.

removeTracker removes a tracker from the package's empty torrent.

#torrent.save(any path) boolean

Deprecated. Deprecated: use torrent.save(path) on a torrent instead.

save writes the package's empty torrent, returning false on failure.

#torrent.seed(any torrentPath, any port) result<unit, error>

Deprecated. Deprecated: use torrent.seedFrom(dir, options?) on a torrent instead.

seed fails with UnsupportedError.

#torrent.setFiles(object[] files)

Deprecated. Deprecated: build a torrent with torrent.fromPath instead.

setFiles replaces the package's empty torrent's files.

#torrent.strip(any metadata) *torrent.Torrent

Deprecated. Deprecated: keep extra data in your own object instead.

strip removes a value stored on the package's empty torrent.

#torrent.validate() boolean

Deprecated. Deprecated: torrents from torrent.fromPath and torrent.decode are always valid.

validate always returns false.

*torrent.Torrent

#torrent.addMetadata(any key, any value)

Deprecated. Deprecated: keep extra data in your own object instead.

addMetadata stores a key and value with the torrent, not in its file.

#torrent.addTracker(string url) boolean

addTracker adds an announce URL, mutating the torrent. It returns false, changing nothing, when the URL is already there. Trackers are outside the info dictionary, so the info hash doesn't change.

#torrent.buildTorrent() string

Deprecated. Deprecated: use torrent.encode() instead, which returns byte[].

buildTorrent returns the .torrent file as text.

#torrent.calculateInfoHash() string

Deprecated. Deprecated: use torrent.infoHash() instead.

calculateInfoHash returns the info hash.

#torrent.clone() *torrent.Torrent

clone returns an independent copy of the torrent, so changing its trackers leaves the original alone.

#torrent.create(any name, array files, any pieceLength) *torrent.Torrent

Deprecated. Deprecated: use torrent.fromPath(path, options?) instead.

create builds a new torrent from local file paths.

#torrent.createFromDirectory(any dirPath) *torrent.Torrent

Deprecated. Deprecated: use torrent.fromPath(path) instead.

createFromDirectory builds a new torrent from a directory.

#torrent.download(any torrentPath, any outputPath) result<unit, error>

Deprecated. Deprecated: use torrent.downloadTo(dir, options?) instead, which really downloads.

download only ever checked the torrent and downloaded nothing; it now fails with UnsupportedError.

#torrent.downloadTo(string dir, object? options) result<{files: {length: int, path: string}[], infoHash: string, name: string, path: string, size: int}, error>

downloadTo downloads the torrent into dir and returns {name, infoHash, path, size, files}. Existing data is verified and kept, so a download resumes. Options: timeout (milliseconds), peers and the newClient options. Fails with TimeoutError, IOError, PermissionError, NetworkError or ParseError.

#torrent.encode() byte[]

encode returns the bytes of the .torrent file.

#torrent.exportInfo(any path) boolean

Deprecated. Deprecated: write the values you need with std:json and std:fs instead.

exportInfo writes getInfo() as JSON to path, returning false on failure.

#torrent.files() {length: int, path: string}[]

files returns the torrent's files, each with path (relative to the torrent's directory, with "/" separators; the name for a single-file torrent) and length (bytes).

#torrent.generatePeerID() string

Deprecated. Deprecated: torrent.newClient makes its own peer ID.

generatePeerID returns "OSL-" and 12 random hex digits.

#torrent.getAllMetadata() object

Deprecated. Deprecated: keep extra data in your own object instead.

getAllMetadata returns every stored key and value.

#torrent.getFileCount() int

Deprecated. Deprecated: use torrent.files().len instead.

getFileCount returns the number of files.

#torrent.getFiles() object[]

Deprecated. Deprecated: use torrent.files() instead.

getFiles returns the files as {path, length, md5sum?} objects.

#torrent.getInfo() object

Deprecated. Deprecated: use name(), pieceLength() and files() instead.

getInfo returns the torrent's info as an object with name, piece_length and files.

#torrent.getMagnetURI() string

Deprecated. Deprecated: use torrent.magnetURI() instead.

getMagnetURI returns the magnet link.

#torrent.getMetadata(any key) string

Deprecated. Deprecated: keep extra data in your own object instead.

getMetadata returns a stored value, or "".

#torrent.getPieceCount() int

Deprecated. Deprecated: use torrent.pieceCount() instead.

getPieceCount returns the number of pieces.

#torrent.getPieceHashes() string[]

Deprecated. Deprecated: use torrent.pieces() instead.

getPieceHashes returns the piece hashes.

#torrent.getTotalSize() int64

Deprecated. Deprecated: use torrent.size() instead.

getTotalSize returns the total size in bytes.

#torrent.getTrackers() array

Deprecated. Deprecated: use torrent.trackers() instead.

getTrackers returns the announce URLs.

#torrent.infoHash() string

infoHash returns the torrent's BitTorrent v1 info hash as 40 lowercase hex digits, the ID clients and trackers know it by.

#torrent.isPrivate() boolean

isPrivate reports whether the torrent is private, which tells clients to find peers only through its trackers.

#torrent.magnetURI() string

magnetURI returns a magnet link for the torrent, with its info hash, its name (dn) and its trackers (tr), URL-encoded.

#torrent.merge(*torrent.Torrent otherTorrent) *torrent.Torrent

Deprecated. Deprecated: build a torrent of a directory with torrent.fromPath instead.

merge builds a torrent of both torrents' files, rereading them from disk.

#torrent.name() string

name returns the torrent's name: the file name of a single-file torrent, or the directory name of a multi-file one.

#torrent.parse(any torrentData) *torrent.Torrent

Deprecated. Deprecated: use torrent.decode(data) instead.

parse reads a new torrent from bencoded text.

#torrent.pieceCount() int

pieceCount returns the number of pieces.

#torrent.pieceLength() int

pieceLength returns the size of each piece in bytes; the last piece may be shorter.

#torrent.pieces() string[]

pieces returns the SHA-1 hash of each piece as 40 hex digits, in order.

#torrent.removeTracker(string url) boolean

removeTracker removes an announce URL, mutating the torrent. It returns false when the URL wasn't there.

#torrent.save(string path) result<unit, error>

save writes the .torrent file to path. Fails with NotFoundError, PermissionError or IOError when path can't be written.

#torrent.seed(any torrentPath, any port) result<unit, error>

Deprecated. Deprecated: use torrent.seedFrom(dir, options?) instead, which really seeds.

seed only ever returned true and seeded nothing; it now fails with UnsupportedError.

#torrent.seedFrom(string dir, object? options) result<*torrent.Download, error>

seedFrom starts uploading the torrent to peers from its data in dir: the file of a single-file torrent or the directory of a multi-file one, under the torrent's name, as torrent.fromPath(dir + "/" + name) made it. It verifies the data first, then seeds in the background until the returned download is closed; its client() tells the port peers connect to. Options are the torrent.newClient options other than dir and seed, such as listenPort and uploadRateLimit. Fails with NotFoundError when a file is missing, ValueError when the data doesn't match the torrent, and NetworkError when the client can't listen.

#torrent.setFiles(object[] files)

Deprecated. Deprecated: build a new torrent with torrent.fromPath instead.

setFiles replaces the file list without rehashing the pieces.

#torrent.size() int

size returns the total size of the torrent's files in bytes.

#torrent.strip(any metadata) *torrent.Torrent

Deprecated. Deprecated: keep extra data in your own object instead.

strip removes a stored metadata key and returns the torrent.

#torrent.trackers() string[]

trackers returns the announce URLs, the first being the primary one.

#torrent.validate() boolean

Deprecated. Deprecated: torrents from torrent.fromPath and torrent.decode are always valid.

validate reports whether the torrent has a name, files, and one valid hash per piece.

*torrent.Client

#client.addMagnet(string uri, object? options) result<*torrent.Download, error>

addMagnet starts downloading the torrent a magnet URI names: it fetches the torrent's metadata from peers (see Download.waitForInfo), then its data, into the client's dir (or the dir option), and returns the download, which runs in the background. Peers come from the URI's trackers and x.pe addresses, the DHT, and the peers option (an array of "host:port" addresses). Adding a torrent the client already has returns its existing download. Fails with ParseError for an invalid magnet URI or peer address and NetworkError when a peer's host can't be resolved. Throws StateError on a closed client.

#client.addTorrent(*torrent.Torrent t, object? options) result<*torrent.Download, error>

addTorrent starts downloading t into the client's dir (or the dir option), first verifying any of its data already there, and returns the download, which runs in the background. Options: dir (a directory for this download instead of the client's) and peers (an array of "host:port" addresses to connect to, besides those found through trackers and the DHT). Adding a torrent the client already has returns its existing download. Fails with ParseError for a malformed peer address and NetworkError when a peer's host can't be resolved. Throws StateError on a closed client.

#client.close()

close stops every download, disconnects from all peers and stops listening. Downloaded files stay on disk. Closing twice is a no-op; using the client or its downloads afterwards throws StateError.

#client.dir() string

dir returns the absolute path of the directory downloads are stored in unless added with their own dir option.

#client.downloads() *torrent.Download[]

downloads returns the client's open downloads, in the order they were added. Throws StateError on a closed client.

#client.port() int

port returns the port the client listens on for peers, which is the listenPort option or, when that was 0, the port the system chose. Throws StateError on a closed client.

*torrent.Download

#download.addPeers(string[] addresses) result<unit, error>

addPeers connects to more peers, given as an array of "host:port" strings. Fails with ParseError for a malformed address and NetworkError when a host can't be resolved, adding none of them. Throws TypeError for an element that isn't a string, and StateError on a closed download.

#download.client() *torrent.Client

client returns the client the download belongs to; for a download from Torrent.seedFrom, that is the client seeding it, whose port() peers connect to.

#download.close()

close stops the download and removes it from its client; files already written stay on disk. For a download from Torrent.seedFrom this stops seeding. Closing twice is a no-op; using the download afterwards throws StateError.

#download.files() {bytesCompleted: int, length: int, path: string}[]

files returns the torrent's files, each with path (relative to the torrent's directory, with "/" separators; the name for a single-file torrent), length (bytes) and bytesCompleted. Throws StateError on a closed download, or while a magnet download's metadata hasn't arrived.

#download.infoHash() string

infoHash returns the torrent's info hash as 40 lowercase hex digits. Throws StateError on a closed download.

#download.isComplete() boolean

isComplete reports whether all of the torrent's data is downloaded and verified. It is false on a closed download.

#download.metainfo() *torrent.Torrent

metainfo returns the torrent's metainfo, the contents of its .torrent file, which for a magnet download is only known once its metadata has arrived. Throws StateError on a closed download, or before then.

#download.name() string

name returns the torrent's name. For a magnet download whose metadata hasn't arrived, it is the URI's display name (dn), or "" when it has none. Throws StateError on a closed download.

#download.path() string

path returns where the torrent's data goes: the file of a single-file torrent or the directory of a multi-file one, as an absolute path. Throws StateError on a closed download, or while a magnet download's metadata hasn't arrived.

#download.pause()

pause stops downloading and uploading the torrent, keeping its peers and data, until resume. Throws StateError on a closed download.

#download.progress() {bytesCompleted: int, bytesTotal: int?, downloadRate: int, fraction: number, peers: int, seeders: int, uploadRate: int}

progress returns the download's state: bytesCompleted and bytesTotal (bytes; bytesTotal is null until a magnet download has its metadata), fraction (0 to 1), peers (connected peers), seeders (connected peers that have the whole torrent), and downloadRate and uploadRate (bytes per second, averaged since the previous call at least half a second earlier). Throws StateError on a closed download.

#download.resume()

resume restarts a paused download. Throws StateError on a closed download.

#download.wait(object? options) result<unit, error>

wait blocks until the whole torrent is downloaded and verified (at once for a complete one). Options: timeout (milliseconds, default 0 for no limit). Fails with TimeoutError when the timeout passes; IOError (or PermissionError) when the data can't be written to disk, which also stops the download; and StateError when the download is closed meanwhile. Throws StateError on a closed download.

#download.waitForInfo(object? options) result<unit, error>

waitForInfo blocks until a magnet download has the torrent's metadata from a peer; a download added from a torrent has it from the start. Options: timeout (milliseconds, default 0 for no limit). Fails with TimeoutError when the timeout passes and StateError when the download is closed meanwhile. Throws StateError on a closed download.