Simple BitTorrent client built with Python's asyncio
A project I made for Python course.
- Downloading torrents and sharing received data
- Graphical interface (supports Drag'n'Drop and can be assigned to *.torrent files in system "Open with..." dialog)
- Console interface
- Pausing torrents, watching progress, download and upload speed, ETA
- Selecting which files in a torrent you want to download
- Saving state between program restarts
Implemented specifications:
- The BitTorrent Protocol Specification (BEP 0003 with some additions from the community spec)
- Multitracker Metadata Extension (BEP 0012)
- (partially) UDP Tracker Protocol (BEP 0015)
- Tracker Returns Compact Peer Lists (BEP 0023)
In this project, I tried to avoid threads and use only asynchronous I/O. As a result, all algorithms and network interaction work in one thread running an asyncio event loop, but there are still a few additional threads:
- Non-blocking disk I/O isn't supported by asyncio. To prevent freezes for up to a second during disk writing, blocking I/O runs in a ThreadPoolExecutor.
- PyQt GUI runs in the main thread and invokes an asyncio event loop in a separate QThread. Another option is to use a Qt event loop in asyncio with quamash, but this increases UI reaction time, and the Qt event loop may be less efficient than asyncio's default one.
Program sources depend on Python 3.5+ features: they are annotated with type hints (PEP 0484) and
use coroutines with async
/await
syntax (PEP 0492).
The program works on Linux, macOS, and Windows. It requires:
- Python 3.5+
aiohttp
,bencodepy
, andbitarray
modules- PyQt5 (only for GUI)
On Ubuntu 16.04 or newer, the requirements can be installed with the following commands:
# apt-get install python3-pip python3-pyqt5
# python3 -m pip install aiohttp bitarray bencodepy
After installing the requirements, clone this repository:
$ git clone https://github.com/borzunov/bit-torrent.git && cd bit-torrent
Run:
$ python3 torrent_gui.py
If torrent files are provided as command line arguments, corresponding adding dialogs will be opened.
You can't start multiple GUI instances, but you can use this command to add more torrents (open the adding dialogs) to the first one.
-
Run a daemon in a separate terminal:
$ python3 torrent_cli.py start
-
(optional) Look at a list of files in a torrent you want to download:
$ python3 torrent_cli.py show ~/Torrents/debian-8.3.0-i386-netinst.iso.torrent
-
Specify a download directory and add the torrent to the daemon:
$ python3 torrent_cli.py add ~/Torrents/debian-8.3.0-i386-netinst.iso.torrent -d ~/Downloads
If the torrent contains more than one file, you can select which files you want to download using
--include
and--exclude
options. For more information run:$ python3 torrent_cli.py add --help
-
Watch torrent status:
$ watch python3 torrent_cli.py status
Add
-v
to increase output verbosity.You also can add more torrents, pause, resume, and remove them. For more information run:
$ python3 torrent_cli.py --help
-
To stop the daemon run:
$ python3 torrent_cli.py stop
The daemon will recover its state after restart.
You can't start multiple daemons or start a console daemon when a GUI instance is already running,
but you can use all console commands (except stop
) to control the GUI daemon from the console.
Note: Both GUI and CLI daemons binds one localhost port in range 6995-6999 for interprocess communication. Don't make this port available outside the localhost, because it can be used to execute arbitrary commands on your machine.
You can enable a verbose debug mode for GUI and CLI daemons by adding --debug
flag after the script name.
You may also want to enable asyncio debug mode. This is done as follows:
$ PYTHONASYNCIODEBUG=1 python3 -Wdefault torrent_gui.py --debug
Copyright © 2016-2017 Alexander Borzunov