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