script.module.pyrrent/lib/pyrrent.py

131 lines
4.4 KiB
Python

import torrent_parser as tp
import threading
from math import ceil
from hashlib import sha1
from datetime import datetime
BUFFER_SIZE = 10 * 1024 * 1024
class Cron(object):
alive = False
def __init__(self, interval, func, *a, **kw):
self.interval = interval
self.func = func
self.a = a
self.kw = kw
self.ticker = threading.Event()
def start(self):
self.t = threading.Thread(target=self.run)
self.alive = True
self.t.start()
def run(self):
while self.alive and not self.ticker.wait(self.interval):
self.func(*self.a, **self.kw)
def stop(self):
self.alive = False
class Buffer(object):
def __init__(self, piece_size):
self.buf_reset_pending = False
self.buf_changed = threading.Event()
self.size = ceil(BUFFER_SIZE / piece_size)
self.buf = []
def buf_changed_event(self):
self.buf_changed.set()
self.buf_changed.clear()
def put(self, piece):
if not self.buf_reset_pending:
self.buf.append(piece)
self.buf_changed_event()
return True
else:
return False
def get(self):
if not self.buf_reset_pending:
if len(self.buf):
return self.buf.pop(0)
return None
def reset(self):
self.buf_reset_pending = True
self.buf = []
self.buf_reset_pending = False
self.buf_changed_event()
def pieces(self):
if not self.buf_reset_pending:
return list(map(lambda x: (x.index, x.hash), self.buf))
else:
return []
class Piece(object):
def __init__(self, p_hash, payload=b''):
self.hash = p_hash
self.payload = payload
self.exist = False
class PieceRange(object):
def __init__(self, first_piece, first_piece_offset, last_piece, last_piece_size):
self.first = first_piece
self.first_offset = first_piece_offset
self.last = last_piece
self.last_size = last_piece_size
class TFile(object):
def __init__(self, path, length, piece_range):
self.path = path
self.name = path[-1]
self.length = length
self.pieace_range = piece_range
class Tracker(object):
def __init__(self, url):
self.url = url
self.next_announce = datetime.now()
self.reachable = True
@property
def can_announce(self):
return self.next_announce <= datetime.now() and self.reachable
class Info(object):
def __init__(self, info, trackers):
trackers = list(set(trackers))
self.hash = sha1(tp.BEncoder(info).encode()).hexdigest()
self.files = []
self.trackers = list(map(Tracker, trackers))
self.pieces = list(map(Piece, info['pieces']))
self.piece_length = info['piece length']
def piece_range_gen(start, next_at):
first_piece = start // self.piece_length
first_piece_offset = start % self.piece_length
tail = next_at % self.piece_length
last_piece = next_at // self.piece_length - int(not bool(tail))
last_piece_size = self.piece_length if not tail else tail
return PieceRange(first_piece, first_piece_offset, last_piece, last_piece_size)
if 'files' in info:
addr_ptr = 0
for i in range(len(info['files'])):
f = info['files'][i]
next_at = addr_ptr + f['length']
pr = piece_range_gen(addr_ptr, next_at)
self.files.append(TFile(info['name'] + f['path'], f['length'], pr))
addr_ptr += f['length']
else:
pr = piece_range_gen(0, info['length'])
self.files.append(TFile([info['name']], info['length'], pr))
class Pyrrent(object):
shuttingdown = False
downloaded = 0
uploaded = 0
tracker_responce = {}
tracker_responce_ver = 0
def __init__(self, filepath):
data = tp.parse_torrent_file(filepath)
self.info = Info(data['info'], [data['announce']] + data['announce-list'] if 'announce-list' in data else [])
def tracker_anouncer(self):
self.tracker_responce_ver += 1
for i in range(len(self.info.trackers)):
tracker = self.info.trackers[i]
if tracker.can_announce:
thr = threading.Thread(target=self.announce, args=(tracker, i))
thr.start()
def announce(self, tracker, index):
pass