# -*- coding: utf-8 -*- import os import chardet try: from python_libtorrent import get_libtorrent # @UnresolvedImport lt = get_libtorrent() print(('Imported libtorrent v%s from python_libtorrent' % (lt.version,))) except Exception as e: print(('Error importing python_libtorrent.Exception: %s' % (str(e),))) try: import libtorrent as lt # @UnresolvedImport except Exception as e: strerror = e.args print(strerror) raise from random import SystemRandom import time import urllib.request, urllib.parse, urllib.error import http.server import socketserver import threading import io from .util import localize_path, Struct, detect_media_type, uri2path, encode_msg if os.getenv('ANDROID_ROOT'): from ctypes import * libc = CDLL('/system/lib/libc.so') libc.lseek64.restype = c_ulonglong libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint] libc.read.restype = c_long libc.read.argtypes = [c_uint, c_void_p, c_long] O_RDONLY = 0 O_LARGEFILE = 0x8000 ###################################################################################### if not hasattr(os, 'getppid'): import ctypes TH32CS_SNAPPROCESS = 0x02 CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @UndefinedVariable MAX_PATH = 260 _kernel32dll = ctypes.windll.Kernel32 CloseHandle = _kernel32dll.CloseHandle class PROCESSENTRY32(ctypes.Structure): _fields_ = [ ("dwSize", ctypes.c_ulong), ("cntUsage", ctypes.c_ulong), ("th32ProcessID", ctypes.c_ulong), ("th32DefaultHeapID", ctypes.c_int), ("th32ModuleID", ctypes.c_ulong), ("cntThreads", ctypes.c_ulong), ("th32ParentProcessID", ctypes.c_ulong), ("pcPriClassBase", ctypes.c_long), ("dwFlags", ctypes.c_ulong), ("szExeFile", ctypes.c_wchar * MAX_PATH) ] Process32First = _kernel32dll.Process32FirstW Process32Next = _kernel32dll.Process32NextW def getppid(): ''' :return: The pid of the parent of this process. ''' pe = PROCESSENTRY32() pe.dwSize = ctypes.sizeof(PROCESSENTRY32) mypid = GetCurrentProcessId() snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) result = 0 try: have_record = Process32First(snapshot, ctypes.byref(pe)) while have_record: if mypid == pe.th32ProcessID: result = pe.th32ParentProcessID break have_record = Process32Next(snapshot, ctypes.byref(pe)) finally: CloseHandle(snapshot) return result os.getppid = getppid ################################################################################# AVOID_HTTP_SERVER_EXCEPTION_OUTPUT = True VERSION = "0.6.0" # USER_AGENT = "pyrrent2http/" + VERSION + " libtorrent/" + lt.version USER_AGENT = 'libtorrent/1.0.9.0' VIDEO_EXTS = {'.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska', '.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg', '.ogv': 'video/ogg', '.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp'} ###################################################################################### class Ticker(object): def __init__(self, interval): self.tick = False self._timer = None self.interval = interval self.is_running = False self.start() @property def true(self): if self.tick: self.tick = False return True else: return False def _run(self): self.is_running = False self.start() self.tick = True def start(self): if not self.is_running: self._timer = threading.Timer(self.interval, self._run) self._timer.start() self.is_running = True def stop(self): self._timer.cancel() self.is_running = False ####################################################################################### class TorrentFile(object): tfs = None closed = True save_path = str() fileEntry = None index = 0 filePtr = None downloaded = 0 progress = 0.0 pdl_thread = None def __init__(self, tfs, fileEntry, savePath, index): self.tfs = tfs self.fileEntry = fileEntry self.name = self.fileEntry.path self.unicode_name = isinstance(self.name, str) and self.name or self.name.decode(chardet.detect(self.name)['encoding']) self.media_type = detect_media_type(self.unicode_name) self.save_path = savePath self.index = index self.piece_length = int(self.tfs.info.piece_length()) self.size = self.fileEntry.size self.offset = self.fileEntry.offset self.startPiece, self.endPiece = self.Pieces() self.pieces_deadlined = [False] * (self.endPiece - self.startPiece) def Downloaded(self): return self.downloaded def Progress(self): return self.progress def __fileptr_(self): if self.closed: return None if self.filePtr is None: while not os.path.exists(self.save_path): logging.info('Waiting for file: %s' % (self.save_path,)) self.tfs.handle.flush_cache() time.sleep(0.5) if os.getenv('ANDROID_ROOT'): self.filePtr = libc.open(self.save_path, O_RDONLY | O_LARGEFILE, 755) else: self.filePtr = io.open(self.save_path, 'rb') return self.filePtr def log(self, message): fnum = self.tfs.openedFiles.index(self) logging.info("[Thread No.%d] %s\n" % (fnum, message)) def Pieces(self): startPiece, _ = self.pieceFromOffset(1) endPiece, _ = self.pieceFromOffset(self.size - 1) return startPiece, endPiece def SetPriority(self, priority): self.tfs.setPriority(self.index, priority) def readOffset(self): if os.getenv('ANDROID_ROOT'): return libc.lseek64(self.filePtr, 0, os.SEEK_CUR) else: return self.filePtr.seek(0, os.SEEK_CUR) def havePiece(self, piece): return self.tfs.handle.have_piece(piece) def pieceFromOffset(self, offset): piece = int((self.offset + offset) / self.piece_length) pieceOffset = int((self.offset + offset) % self.piece_length) return piece, pieceOffset def waitForPiece(self, piece): def set_deadlines(p): next_piece = p + 1 BUF_SIZE = 2 # Лучшее враг хорошего for i in range(BUF_SIZE): if (next_piece + i < self.endPiece and not self.pieces_deadlined[(next_piece + i) - self.startPiece] and not self.havePiece( next_piece + i)): self.tfs.handle.set_piece_deadline(next_piece + i, 70 + (20 * i)) self.pieces_deadlined[(next_piece + i) - self.startPiece] = True if not self.havePiece(piece): self.log('Waiting for piece %d' % (piece,)) self.tfs.handle.set_piece_deadline(piece, 50) while not self.havePiece(piece): if self.tfs.handle.piece_priority(piece) == 0 or self.closed: return False time.sleep(0.1) if not isinstance(self.pdl_thread, threading.Thread) or not self.pdl_thread.is_alive(): self.pdl_thread = threading.Thread(target=set_deadlines, args=(piece,)) self.pdl_thread.start() return True def Close(self): if self.closed: return self.log('Closing %s...' % (self.name,)) self.tfs.removeOpenedFile(self) self.closed = True if self.filePtr is not None: if os.getenv('ANDROID_ROOT'): libc.close(self.filePtr) else: self.filePtr.close() self.filePtr = None def ShowPieces(self): pieces = self.tfs.handle.status().pieces str_ = '' for i in range(self.startPiece, self.endPiece + 1): if pieces[i] == False: str_ += "-" else: str_ += "#" self.log(str_) def Read(self, buf): filePtr = self.__fileptr_() if filePtr is None: raise IOError toRead = len(buf) if toRead > self.piece_length: toRead = self.piece_length readOffset = self.readOffset() startPiece, _ = self.pieceFromOffset(readOffset) endPiece, _ = self.pieceFromOffset(readOffset + toRead) for i in range(startPiece, endPiece + 1): if not self.waitForPiece(i): raise IOError if os.getenv('ANDROID_ROOT'): read = libc.read(self.filePtr, addressof(buf), toRead) else: read = filePtr.readinto(buf) return read def Seek(self, offset, whence): filePtr = self.__fileptr_() if filePtr is None: return if whence == os.SEEK_END: offset = self.size - offset whence = os.SEEK_SET if os.getenv('ANDROID_ROOT'): newOffset = libc.lseek64(self.filePtr, offset, whence) else: newOffset = filePtr.seek(offset, whence) self.log('Seeking to %d/%d' % (newOffset, self.size)) return newOffset def IsComplete(self): return self.downloaded == self.size ####################################################################################### class TorrentFS(object): handle = None info = None priorities = list() openedFiles = list() lastOpenedFile = None shuttingDown = False fileCounter = int() progresses = list() save_path = None def __init__(self, root, handle): self.root = root self.handle = handle self.waitForMetadata() self.save_path = localize_path(self.root.torrentParams['save_path']) self.priorities = list(self.handle.file_priorities()) self.files = {} num_files = self.info.num_files() for i in range(num_files): self.setPriority(i, 0) def file(self, index): for name in list(self.files.keys()): if self.files[name].index == index: return self.files[name] file_ = self.__file_at_(index) self.files[file_.name] = file_ self.setPriority(index, 1) return file_ def Shutdown(self): self.shuttingDown = True if len(self.openedFiles) > 0: logging.info('Closing %d opened file(s)' % (len(self.openedFiles),)) for f in self.openedFiles: f.Close() def addOpenedFile(self, file_): self.openedFiles.append(file_) def setPriority(self, index, priority): if self.priorities[index] != priority: logging.info('Setting %s priority to %d' % (self.info.file_at(index).path, priority)) self.priorities[index] = priority self.handle.file_priority(index, priority) def findOpenedFile(self, file): for i, f in enumerate(self.openedFiles): if f == file: return i return -1 def removeOpenedFile(self, file): pos = self.findOpenedFile(file) if pos >= 0: del self.openedFiles[pos] def waitForMetadata(self): if not self.handle.status().has_metadata: time.sleep(0.1) try: self.info = self.handle.torrent_file() except: self.info = self.handle.get_torrent_info() def HasTorrentInfo(self): return self.info is not None def LoadFileProgress(self): self.progresses = self.handle.file_progress() for k in list(self.files.keys()): self.files[k].downloaded = self.getFileDownloadedBytes(self.files[k].index) if self.files[k].size > 0: self.files[k].progress = float(self.files[k].downloaded) / float( self.files[k].size) def getFileDownloadedBytes(self, i): try: bytes_ = self.progresses[i] except IndexError: bytes_ = 0 return bytes_ def __files_(self): info = self.info files_ = [] for i in range(info.num_files()): file_ = self.__file_at_(i) file_.downloaded = self.getFileDownloadedBytes(i) if file_.size > 0: file_.progress = float(file_.downloaded) / float(file_.size) files_.append(file_) return files_ def __file_at_(self, index): info = self.info fileEntry = info.file_at(index) fe_path = fileEntry.path path = os.path.abspath(os.path.join(self.save_path, localize_path(fe_path))) return TorrentFile( self, fileEntry, path, index ) def FileByName(self, name): for i, f in enumerate(self.info.files()): if f.path == name: return self.__file_at_(i) raise IOError def Open(self, name): if self.shuttingDown or not self.HasTorrentInfo(): raise IOError return self.OpenFile(name) def checkPriorities(self): for index, priority in enumerate(self.priorities): if priority == 0: continue found = False for f in self.openedFiles: if f.index == index: found = True break if not found: self.setPriority(index, 0) def OpenFile(self, name): try: tf = self.FileByName(name) except IOError: return tf.closed = False self.fileCounter += 1 tf.num = self.fileCounter self.addOpenedFile(tf) tf.log('Opened %s...' % (tf.name,)) tf.SetPriority(1) self.handle.set_piece_deadline(tf.startPiece, 50) self.lastOpenedFile = tf self.files[tf.name] = tf self.checkPriorities() return tf ############################################################# class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): def handle_error(self, *args, **kwargs): '''Обходим злосчастный "Broken Pipe" и прочие трейсы''' if not AVOID_HTTP_SERVER_EXCEPTION_OUTPUT: http.server.HTTPServer.handle_error(self, *args, **kwargs) def HttpHandlerFactory(): class HttpHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): # print ('---Headers---\n%s\n' % (self.headers,)) # print ('---Request---\n%s\n' % (self.path,)) if self.path.startswith('/files/'): self.filesHandler() else: self.send_error(404, 'Not found') self.end_headers() def filesHandler(self): f, start_range, end_range = self.send_head() if not f.closed: f.Seek(start_range, 0) chunk = f.piece_length total = 0 if os.getenv('ANDROID_ROOT'): buf = create_string_buffer(chunk) else: buf = bytearray(chunk) while chunk > 0 and not self.server.root_obj.forceShutdown: if start_range + chunk > end_range: chunk = end_range - start_range if os.getenv('ANDROID_ROOT'): buf = create_string_buffer(chunk) else: buf = bytearray(chunk) try: if f.Read(buf) < 1: break while self.server.root_obj.pause and not self.server.root_obj.forceShutdown: time.sleep(0.1) continue if os.getenv('ANDROID_ROOT'): self.wfile.write(buf.raw) else: self.wfile.write(buf) except: break total += chunk start_range += chunk f.Close() def send_head(self): fname = urllib.parse.unquote(self.path.lstrip('/files/')) try: f = self.server.root_obj.TorrentFS.Open(fname) except IOError: self.send_error(404, "File not found") return (None, 0, 0) _, ext = os.path.splitext(fname) ctype = (ext != '' and ext in list(VIDEO_EXTS.keys())) and VIDEO_EXTS[ext] or 'application/octet-stream' if "Range" in self.headers: self.send_response(206, 'Partial Content') else: self.send_response(200) self.send_header("Content-type", ctype) self.send_header('transferMode.dlna.org', 'Streaming') size = f.size start_range = 0 end_range = size self.send_header("Accept-Ranges", "bytes") if "Range" in self.headers: s, e = self.headers['range'][6:].split('-', 1) sl = len(s) el = len(e) if sl > 0: start_range = int(s) if el > 0: end_range = int(e) + 1 elif el > 0: ei = int(e) if ei < size: start_range = size - ei self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size)) self.send_header("Content-Length", end_range - start_range) self.send_header("Last-Modified", self.date_time_string(f.fileEntry.mtime)) self.end_headers() # print "Sending Bytes ",start_range, " to ", end_range, "...\n" return f, start_range, end_range # Вырубаем access-log def log_message(self, fmt, *args): return return HttpHandler class Pyrrent2http(object): pause = False def __init__(self, uri='', bindAddress='localhost:5001', downloadPath='.', idleTimeout=-1, keepComplete=False, keepIncomplete=False, keepFiles=False, showAllStats=False, showOverallProgress=False, showFilesProgress=False, showPiecesProgress=False, debugAlerts=False, exitOnFinish=False, resumeFile='', stateFile='', userAgent=USER_AGENT, dhtRouters='', trackers='', listenPort=6881, torrentConnectBoost=50, connectionSpeed=50, peerConnectTimeout=15, requestTimeout=20, maxDownloadRate=-1, maxUploadRate=-1, connectionsLimit=200, encryption=1, minReconnectTime=60, maxFailCount=3, noSparseFile=False, randomPort=False, enableScrape=False, enableDHT=True, enableLSD=True, enableUPNP=True, enableNATPMP=True, enableUTP=True, enableTCP=True, proxy=None): self.torrentHandle = None self.forceShutdown = False self.session = None self.magnet = False self.config = Struct() self.config.uri = uri self.config.bindAddress = bindAddress self.config.downloadPath = downloadPath self.config.idleTimeout = idleTimeout self.config.keepComplete = keepComplete self.config.keepIncomplete = keepIncomplete self.config.keepFiles = keepFiles self.config.showAllStats = showAllStats self.config.showOverallProgress = showOverallProgress self.config.showFilesProgress = showFilesProgress self.config.showPiecesProgress = showPiecesProgress self.config.debugAlerts = debugAlerts self.config.exitOnFinish = exitOnFinish self.config.resumeFile = resumeFile self.config.stateFile = stateFile self.config.userAgent = userAgent self.config.dhtRouters = dhtRouters self.config.trackers = trackers self.config.listenPort = listenPort self.config.torrentConnectBoost = torrentConnectBoost self.config.connectionSpeed = connectionSpeed self.config.peerConnectTimeout = peerConnectTimeout self.config.requestTimeout = requestTimeout self.config.maxDownloadRate = maxDownloadRate self.config.maxUploadRate = maxUploadRate self.config.connectionsLimit = connectionsLimit self.config.encryption = encryption self.config.minReconnectTime = minReconnectTime self.config.maxFailCount = maxFailCount self.config.noSparseFile = noSparseFile self.config.randomPort = randomPort self.config.enableScrape = enableScrape self.config.enableDHT = enableDHT self.config.enableLSD = enableLSD self.config.enableUPNP = enableUPNP self.config.enableNATPMP = enableNATPMP self.config.enableUTP = enableUTP self.config.enableTCP = enableTCP self.config.proxy = proxy if self.config.uri == '': raise Exception("uri is empty string") if self.config.uri.startswith('magnet:'): self.magnet = True if self.config.resumeFile is None: self.config.resumeFile = '' if self.config.resumeFile != '' and not self.config.keepFiles: raise Exception('Не должно быть файла восстановления, если мы не храним файлы') def buildTorrentParams(self, uri): try: absPath = uri2path(uri) logging.info('Opening local torrent file: %s' % (encode_msg(absPath),)) torrent_info = lt.torrent_info(lt.bdecode(open(absPath, 'rb').read())) except Exception as e: strerror = e.args logging.error('Build torrent params error is (%s)' % (strerror,)) raise torrentParams = {} torrentParams['ti'] = torrent_info logging.info('Setting save path: %s' % (encode_msg(self.config.downloadPath),)) torrentParams['save_path'] = self.config.downloadPath if os.path.exists(self.config.resumeFile): logging.info('Loading resume file: %s' % (encode_msg(self.config.resumeFile),)) try: with open(self.config.resumeFile, 'rb') as f: torrentParams["auto_managed"] = True torrentParams['resume_data'] = f.read() except Exception as e: strerror = e.args logging.error(strerror) if self.config.noSparseFile or self.magnet: logging.info('Disabling sparse file support...') torrentParams["storage_mode"] = lt.storage_mode_t.storage_mode_allocate return torrentParams def addTorrent(self): self.torrentParams = self.buildTorrentParams(self.config.uri) logging.info('Adding torrent') self.torrentHandle = self.session.add_torrent(self.torrentParams) self.torrentHandle.set_sequential_download(False) # # Хороший флаг, но не в нашем случае. Мы сами указываем, какие куски нам нужны (handle.set_piece_deadline) # Также, у нас перемотка. Т.е. произвольный доступ. # Значит, последовательная загрузка нам будет только вредить # self.torrentHandle.set_max_connections(60) if self.config.trackers != '': trackers = self.config.trackers.split(',') startTier = 256 - len(trackers) for n in range(len(trackers)): tracker = trackers[n].strip() logging.info('Adding tracker: %s' % (tracker,)) self.torrentHandle.add_tracker({'url': tracker}) if self.config.enableScrape: logging.info('Sending scrape request to tracker') self.torrentHandle.scrape_tracker() try: info = self.torrentHandle.torrent_file() except: info = self.torrentHandle.get_torrent_info() logging.info('Downloading torrent: %s' % (info.name(),)) try: self.TorrentFS = TorrentFS(self, self.torrentHandle) except Exception as e: logging.error(e.args) name = self.TorrentFS.info.name() self.torrent_name = name def startHTTP(self): logging.info('Starting HTTP Server...') handler = HttpHandlerFactory() handler.protocol_version = 'HTTP/1.1' logging.info('Listening HTTP on %s...\n' % (self.config.bindAddress,)) host, strport = self.config.bindAddress.split(':') if len(strport) > 0: srv_port = int(strport) self.httpListener = ThreadingHTTPServer((host, srv_port), handler) self.httpListener.root_obj = self self.listener_thread = threading.Thread(target=self.httpListener.serve_forever) self.listener_thread.start() def startServices(self): if self.config.enableDHT: logging.info('Starting DHT...') self.session.start_dht() if self.config.enableLSD: logging.info('Starting LSD...') self.session.start_lsd() if self.config.enableUPNP: logging.info('Starting UPNP...') self.session.start_upnp() if self.config.enableNATPMP: logging.info('Starting NATPMP...') self.session.start_natpmp() def startSession(self): logging.info('Starting session...') self.session = lt.session(lt.fingerprint('LT', lt.version_major, lt.version_minor, 0, 0), flags=int(lt.session_flags_t.add_default_plugins)) alertMask = (lt.alert.category_t.error_notification | lt.alert.category_t.storage_notification | lt.alert.category_t.tracker_notification | lt.alert.category_t.status_notification) if self.config.debugAlerts: alertMask |= lt.alert.category_t.debug_notification self.session.set_alert_mask(alertMask) settings = self.session.get_settings() settings["request_timeout"] = self.config.requestTimeout settings["peer_connect_timeout"] = self.config.peerConnectTimeout settings["announce_to_all_trackers"] = True settings["announce_to_all_tiers"] = True settings["torrent_connect_boost"] = self.config.torrentConnectBoost settings["connection_speed"] = self.config.connectionSpeed settings["min_reconnect_time"] = self.config.minReconnectTime settings["max_failcount"] = self.config.maxFailCount settings["recv_socket_buffer_size"] = 1024 * 1024 settings["send_socket_buffer_size"] = 1024 * 1024 settings["rate_limit_ip_overhead"] = True settings["min_announce_interval"] = 60 settings["tracker_backoff"] = 0 ### Непонятно, как заставить использовать прокси только для подключения к трекеру? if self.config.proxy is not None: ps = lt.proxy_settings() # peer_ps = lt.proxy_settings() # peer_ps.type = lt.proxy_type.none ps.hostname = self.config.proxy['host'] ps.port = self.config.proxy['port'] ps.type = lt.proxy_type.socks5 # self.session.set_peer_proxy(peer_ps) self.session.set_proxy(ps) settings['force_proxy'] = False settings['proxy_peer_connections'] = False settings['anonymous_mode'] = False settings['proxy_tracker_connections'] = True self.session.set_settings(settings) if self.config.stateFile != '': logging.info('Loading session state from %s' % (self.config.stateFile,)) try: with open(self.config.stateFile, 'rb') as f: bytes__ = f.read() except IOError as e: strerror = e.args logging.error(strerror) else: self.session.load_state(lt.bdecode(bytes__)) rand = SystemRandom(time.time()) portLower = self.config.listenPort if self.config.randomPort: portLower = rand.randint(0, 16374) + 49151 portUpper = portLower + 10 try: self.session.listen_on(portLower, portUpper) except IOError as e: strerror = e.args logging.error(strerror) raise settings = self.session.get_settings() if self.config.userAgent != '': settings['user_agent'] = self.config.userAgent if self.config.connectionsLimit >= 0: settings['connections_limit'] = self.config.connectionsLimit if self.config.maxDownloadRate >= 0: settings['download_rate_limit'] = self.config.maxDownloadRate * 1024 if self.config.maxUploadRate >= 0: settings['upload_rate_limit'] = self.config.maxUploadRate * 1024 settings['enable_incoming_tcp'] = self.config.enableTCP settings['enable_outgoing_tcp'] = self.config.enableTCP settings['enable_incoming_utp'] = self.config.enableUTP settings['enable_outgoing_utp'] = self.config.enableUTP self.session.set_settings(settings) if self.config.dhtRouters != '': routers = self.config.dhtRouters.split(',') for router in routers: router = router.strip() if router != '': hostPort = router.split(':') host = hostPort[0].strip() try: port = len(hostPort) > 1 and int(hostPort[1].strip()) or 6881 except ValueError as e: strerror = e.args logging.error(strerror) raise self.session.add_dht_router(host, port) logging.info('Added DHT router: %s:%d' % (host, port)) logging.info('Setting encryption settings') try: encryptionSettings = lt.pe_settings() encryptionSettings.out_enc_policy = lt.enc_policy(self.config.encryption) encryptionSettings.in_enc_policy = lt.enc_policy(self.config.encryption) encryptionSettings.allowed_enc_level = lt.enc_level.both encryptionSettings.prefer_rc4 = True self.session.set_pe_settings(encryptionSettings) except Exception as e: logging.info('Encryption not supported: %s' % (e.args,)) def Status(self): info = self.TorrentFS.info tstatus = self.torrentHandle.status() status = { 'name': self.torrent_name, 'state': int(tstatus.state), 'state_str': str(tstatus.state), 'error': tstatus.error, 'progress': tstatus.progress, 'download_rate': tstatus.download_rate // 1024, 'upload_rate': tstatus.upload_rate // 1024, 'total_download': tstatus.total_download, 'total_upload': tstatus.total_upload, 'num_peers': tstatus.num_peers, 'num_seeds': tstatus.num_seeds, 'total_seeds': tstatus.num_complete, 'total_peers': tstatus.num_incomplete } return status def Ls(self, index): fi = {} if self.TorrentFS.HasTorrentInfo(): x = [n for n in list(self.TorrentFS.files.keys()) if self.TorrentFS.files[n].index == index] name = x[0] files = self.TorrentFS.files Url = 'http://' + self.config.bindAddress + '/files/' + urllib.parse.quote(name) fi = { 'index': files[name].index, 'name': files[name].unicode_name, 'media_type': files[name].media_type, 'size': files[name].size, 'offset': files[name].offset, 'download': files[name].downloaded, 'progress': files[name].progress, 'save_path': files[name].save_path, 'url': Url } return fi def Peers(self): peers = {'peers': []} for peer in self.torrentHandle.get_peer_info(): if peer.flags & peer.connecting or peer.flags & peer.handshake: continue pi = { 'Ip': peer.ip, 'Flags': peer.flags, 'Source': peer.source, 'UpSpeed': peer.up_speed // 1024, 'DownSpeed': peer.down_speed // 1024, 'TotalDownload': peer.total_download, 'TotalUpload': peer.total_upload, 'Country': peer.country, 'Client': peer.client } peers['peers'].append(pi) return peers def consumeAlerts(self): alerts = self.session.pop_alerts() for alert in alerts: if type(alert) == lt.save_resume_data_alert: self.processSaveResumeDataAlert(alert) break def waitForAlert(self, alert_type, timeout): start = time.time() while True: alert = self.session.wait_for_alert(100) if (time.time() - start) > timeout: return None if alert is not None: alert = self.session.pop_alert() if type(alert) == alert_type: return alert def loop(self): self.saveResumeDataTicker = Ticker(5) time_start = time.time() while True: if self.forceShutdown: return if time.time() - time_start > 0.5: self.consumeAlerts() self.TorrentFS.LoadFileProgress() state = self.torrentHandle.status().state if self.config.exitOnFinish and (state == state.finished or state == state.seeding): self.forceShutdown = True if os.getppid() == 1: self.forceShutdown = True time_start = time.time() if self.saveResumeDataTicker.true: self.saveResumeData(True) time.sleep(0.3) def processSaveResumeDataAlert(self, alert): logging.info('Saving resume data to: %s' % (encode_msg(self.config.resumeFile),)) data = lt.bencode(alert.resume_data) try: with open(self.config.resumeFile, 'wb') as f: f.write(data) except IOError as e: strerror = e.args logging.error(strerror) def saveResumeData(self, async_=False): if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '': return False self.torrentHandle.save_resume_data(lt.save_resume_flags_t.flush_disk_cache) if not async_: alert = self.waitForAlert(lt.save_resume_data_alert, 5) if alert is None: return False self.processSaveResumeDataAlert(alert) return True def saveSessionState(self): if self.config.stateFile == '': return entry = self.session.save_state() data = lt.bencode(entry) logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),)) try: logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),)) with open(self.config.stateFile, 'wb') as f: f.write(data) except IOError as e: strerror = e.args logging.error(strerror) def removeFiles(self, files): for file in files: try: os.remove(file) except Exception as e: strerror = e.args logging.error(strerror) else: path = os.path.dirname(file) savePath = os.path.abspath(self.config.downloadPath) savePath = savePath[-1] == os.path.sep and savePath[:-1] or savePath while path != savePath: os.remove(path) path_ = os.path.dirname(path) path = path_[-1] == os.path.sep and path_[:-1] or path_ def filesToRemove(self): files = [] if self.TorrentFS.HasTorrentInfo(): for i, f in enumerate(self.torrentHandle.files()): isComplete = self.TorrentFS.progresses[i] == f.size if (not self.config.keepComplete or not isComplete) and (not self.config.keepIncomplete or isComplete): path = os.path.abspath(os.path.join(self.TorrentFS.save_path, localize_path(f.path))) if os.path.exists(path): files.append(path) return files def removeTorrent(self): files = [] flag = 0 state = self.torrentHandle.status().state if state != state.checking_files and not self.config.keepFiles: if not self.config.keepComplete and not self.config.keepIncomplete: flag = int(lt.options_t.delete_files) else: files = self.filesToRemove() logging.info('Removing the torrent') self.session.remove_torrent(self.torrentHandle, flag) if flag > 0 or len(files) > 0: logging.info('Waiting for files to be removed') self.waitForAlert(lt.torrent_deleted_alert, 15) self.removeFiles(files) def shutdown(self): logging.info('Stopping pyrrent2http...') self.forceShutdown = True self.saveResumeDataTicker.stop() self.httpListener.shutdown() self.httpListener.socket.close() self.TorrentFS.Shutdown() if self.session != None: self.session.pause() self.waitForAlert(lt.torrent_paused_alert, 10) if self.torrentHandle is not None: self.saveResumeData(False) self.saveSessionState() self.removeTorrent() logging.info('Aborting the session') self.session = None logging.info('Bye bye')