Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add seeder.py #7877

Merged
merged 1 commit into from
Feb 2, 2024
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
56 changes: 56 additions & 0 deletions scripts/seedbox/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Seedbox

This folder contains scripts for effortlessly setting up a seedbox.

## Prerequisites

Clone the tribler repo:

```bash
git clone https://github.com/Tribler/tribler.git
```

Install requirements:

```bash
python3 -m pip install -r scripts/seeedbox/requirements.txt
```

## Torrent seeding

To start torrents' seeding run the following script:

```bash
python3 seeder.py <source folder>
```

Consider the following folder structure:

```text
source folder
├ sub_directory
| ├ file1
| └file2
├ sub_directory2
| ├ file3
| └ file4
├ thumbnail.png
└ description.md
```

In this particular example, `seeder.py` will create two torrents:
`sub_directory.torrent` and `sub_directory2.torrent`.

`seeder.py` will start to seed them through BitTorrent protocol after creating.

### Error reporting

In case you want errors to be reported, you can use [Sentry](https://develop.sentry.dev/)

To enable error reporting, specify the following environment variable:

```bash
export SENTRY_URL=<sentry_url>
```

URL can be taken directly from a corresponding Sentry project.
2 changes: 2 additions & 0 deletions scripts/seedbox/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
libtorrent==1.2.19
sentry-sdk==1.31.0
208 changes: 208 additions & 0 deletions scripts/seedbox/seeder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
"""
This script generates torrents in input folder and seed them.
For available parameters see "parse_args" function below.

Folder structure:

# my channel
# ├ sub_directory
# | ├ file1
# | └ file2
# ├ sub_directory2
# | ├ file3
# | └ file4
# ├ file5
# └ file6

The script generates torrents for each folder contains files.
There are a possibility to add ignored files (see "_ignore_glob" below).
"""
import argparse
import logging
import os
import time
from collections import defaultdict
from pathlib import Path

import libtorrent
import sentry_sdk

UNLIMITED = -1

_creator = 'TU Delft'

_dht_routers = [
('router.utorrent.com', 6881),
("router.utorrent.com", 6881),
("router.bittorrent.com", 6881),
("dht.transmissionbt.com", 6881),
("dht.aelitis.com", 6881),
("router.bitcomet.com", 6881),
]
_port_range = (6881, 7000)
_log_statistics_interval_in_sec = 10
_add_torrent_delay_in_sec = 1
_ignore_glob = [
'*DS_Store',
'*.torrent',
'thumbnail.png',
'description.md',
]

_logger = logging.getLogger('Seeder')

sentry_sdk.init(
os.environ.get('SENTRY_URL'),
traces_sample_rate=1.0
)


def parse_args():
parser = argparse.ArgumentParser(description='Seed data by using the LibTorrent protocol')

parser.add_argument('-s', '--source', type=str, help='path to data folder', default='.')
parser.add_argument('-v', '--verbosity', help='increase output verbosity', action='store_true')
parser.add_argument('-t', '--testnet', help='Testnet run', action='store_true')

return parser.parse_args()


def setup_logger(verbosity):
logging_level = logging.DEBUG if verbosity else logging.INFO
logging.basicConfig(level=logging_level)


def get_folders_with_files(source):
""" Return all folders that contains files

Args:
source: a source folder

Returns:
Dictionary where
* key: is a folder
* value: is a file list
"""
result = {}

for file in Path(source).rglob('*'):
ignore = any(file.match(a) for a in _ignore_glob)
if file.is_file() and not ignore:
result.setdefault(file.parent, set()).add(file)

return result


def create_torrents(folders, source):
_logger.info(f'Creating {len(folders)} torrent files...')

for folder in folders:
if folder.match(source):
continue

torrent_file = folder.parent / f'{folder.name}.torrent'

if not torrent_file.exists():
original, encoded = create_torrent_from_folder(folder, folders[folder])
torrent_file.write_bytes(encoded)
_logger.info(f'Created: {torrent_file}')

yield original, folder
else:
_logger.info(f'Skipped (file already exists): {torrent_file}')

encoded = torrent_file.read_bytes()
decoded = libtorrent.bdecode(encoded)

yield decoded, folder


def create_torrent_from_folder(folder, files):
file_storage = libtorrent.file_storage()
file_storage.set_name(folder.name)

for file in files:
relative = file.relative_to(folder.parent)
size = file.stat().st_size

file_storage.add_file(str(relative), size)

flags = libtorrent.create_torrent_flags_t.optimize
torrent = libtorrent.create_torrent(file_storage, flags=flags)

torrent.set_creator(_creator)
libtorrent.set_piece_hashes(torrent, str(folder.parent))

torrent_data = torrent.generate()
return torrent_data, libtorrent.bencode(torrent_data)


def log_all_alerts(session):
for a in session.pop_alerts():
if a.category() & libtorrent.alert.category_t.error_notification:
_logger.error(a)
else:
_logger.info(a)


def log_statistics(session, handlers, interval):
while True:
time.sleep(interval)
log_all_alerts(session)

states = defaultdict(int)
errors = defaultdict(int)

for h in handlers:
status = h.status()
states[status.state] += 1
if status.errc.value() != 0:
errors[status.errc.message()] += 1

_logger.info(f'Torrents states: {states}')
if errors:
_logger.info(f'Torrents errors: {errors}')


def seed(torrents):
_logger.info(f'Create torrent session in port range: {_port_range}')
session = libtorrent.session()
session.listen_on(*_port_range)
for router in _dht_routers:
session.add_dht_router(*router)

session.start_dht()

session.apply_settings({
'active_seeds': UNLIMITED,
'active_limit': UNLIMITED
})

handlers = []
for torrent, folder in torrents:
torrent_info = libtorrent.torrent_info(torrent)
params = {
'save_path': str(folder.parent),
'ti': torrent_info,
'name': folder.name,
}

_logger.info(f'Add torrent: {params}')
result = session.add_torrent(params)
handlers.append(result)

time.sleep(_add_torrent_delay_in_sec)
log_all_alerts(session)

log_statistics(session, handlers, _log_statistics_interval_in_sec)


if __name__ == "__main__":
_arguments = parse_args()
print(f"Arguments: {_arguments}")

setup_logger(_arguments.verbosity)
_folders = get_folders_with_files(_arguments.source)
_torrents = list(create_torrents(_folders, _arguments.source))
if not _arguments.testnet:
seed(_torrents)
Loading