diff --git a/addon.xml b/addon.xml index 68b934a..5d9bbda 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ - + - + @@ -17,5 +17,5 @@ Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent. Provides sequential torrent downloading for online streaming video and other media over HTTP. inpos@yandex.ru - https://github.com/inpos/script.module.pyrrent2http + https://git.ukamnya.ru/ukamnya/script.module.pyrrent2http diff --git a/lib/pyrrent2http/__init__.py b/lib/pyrrent2http/__init__.py index 6b7891d..471f05e 100644 --- a/lib/pyrrent2http/__init__.py +++ b/lib/pyrrent2http/__init__.py @@ -3,33 +3,6 @@ from collections import namedtuple -# noinspection PyClassHasNoInit -class State: - QUEUED_FOR_CHECKING = 0 - CHECKING_FILES = 1 - DOWNLOADING_METADATA = 2 - DOWNLOADING = 3 - FINISHED = 4 - SEEDING = 5 - ALLOCATING = 6 - CHECKING_RESUME_DATA = 7 - - -# noinspection PyClassHasNoInit -class MediaType: - UNKNOWN = None - AUDIO = 'audio' - VIDEO = 'video' - SUBTITLES = 'subtitles' - - -# noinspection PyClassHasNoInit -class Encryption: - FORCED = 0 - ENABLED = 1 - DISABLED = 2 - - SessionStatus = namedtuple('SessionStatus', "name, state, state_str, error, progress, download_rate, upload_rate, " "total_download, total_upload, num_peers, num_seeds, total_seeds, " "total_peers") @@ -39,5 +12,5 @@ FileStatus = namedtuple('FileStatus', "name, save_path, url, size, offset, downl PeerInfo = namedtuple('PeerInfo', "ip, flags, source, up_speed, down_speed, total_upload, total_download, " "country, client") -from engine import Engine -from error import Error +from .engine import Engine +from .error import Error diff --git a/lib/pyrrent2http/engine.py b/lib/pyrrent2http/engine.py index f3e8692..c1871ec 100644 --- a/lib/pyrrent2http/engine.py +++ b/lib/pyrrent2http/engine.py @@ -1,31 +1,37 @@ # -*- coding: utf-8 -*- import os +import threading +import urllib.error +import urllib.parse +import urllib.request + +import chardet import sys import time -import pyrrent2http import xbmc -from error import Error -from . import SessionStatus, FileStatus, PeerInfo, Encryption -from util import can_bind, find_free_port, localize_path, uri2path, detect_media_type -import threading -import urllib -import chardet + +from . import SessionStatus, FileStatus, PeerInfo +from . import pyrrent2http +from .error import Error +from .structs import Encryption +from .util import can_bind, find_free_port, localize_path, uri2path, detect_media_type LOGGING = True + class Engine: """ This is python binding class to pyrrent2http client. """ + def _log(self, message): if self.logger: self.logger(message) else: xbmc.log("[pyrrent2http] %s" % message) - def __init__(self, uri=None, platform=None, download_path=".", - bind_host='127.0.0.1', bind_port=5001, connections_limit=None, download_kbps=None, upload_kbps=None, + bind_host='127.0.0.1', bind_port=5001, connections_limit=200, download_kbps=-1, upload_kbps=-1, enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False, log_stats=False, encryption=Encryption.ENABLED, keep_complete=False, keep_incomplete=False, keep_files=False, log_files_progress=False, log_overall_progress=False, log_pieces_progress=False, @@ -198,7 +204,7 @@ class Engine: 'maxFailCount': self.max_failcount, 'showPiecesProgress': self.log_pieces_progress, 'idleTimeout': self.max_idle_timeout, - #'fileIndex': start_index, + # 'fileIndex': start_index, 'connectionsLimit': self.connections_limit, 'enableScrape': self.enable_scrape, 'enableUTP': self.enable_utp, @@ -208,25 +214,28 @@ class Engine: } self._log("Invoking pyrrent2http") + class Logging(object): def __init__(self, _log): self._log = _log + def info(self, message): if LOGGING: self._log('INFO: %s' % (message,)) + def error(self, message): if LOGGING: self._log('ERROR: %s' % (message,)) + pyrrent2http.logging = Logging(self._log) - + self.pyrrent2http = pyrrent2http.Pyrrent2http(**kwargs) self.pyrrent2http.startSession() self.pyrrent2http.startServices() self.pyrrent2http.addTorrent() self.pyrrent2http.startHTTP() - self.pyrrent2http_loop = threading.Thread(target = self.pyrrent2http.loop) + self.pyrrent2http_loop = threading.Thread(target=self.pyrrent2http.loop) self.pyrrent2http_loop.start() - start = time.time() self.started = True @@ -236,7 +245,7 @@ class Engine: if not self.is_alive(): raise Error("Can't start pyrrent2http, see log for details", Error.PROCESS_ERROR) try: - #self.status(1) + # self.status(1) initialized = True break except Error: @@ -249,9 +258,10 @@ class Engine: def activate_file(self, index): self.pyrrent2http.TorrentFS.file(index) - + def pause(self): self.pyrrent2http.pause = True + def resume(self): self.pyrrent2http.pause = False @@ -279,8 +289,6 @@ class Engine: status = SessionStatus(**status) return status - - def list(self, media_types=None, timeout=10): """ Returns list of files in the torrent (see FileStatus named tuple). @@ -296,8 +304,9 @@ class Engine: if files: res = [FileStatus(index=index, **f) for index, f in enumerate(files)] if media_types is not None: - res = filter(lambda fs: fs.media_type in media_types, res) + res = [fs for fs in res if fs.media_type in media_types] return res + def list_from_info(self, media_types=None): try: info = pyrrent2http.lt.torrent_info(uri2path(self.uri)) @@ -306,21 +315,21 @@ class Engine: files = [] for i in range(info.num_files()): f = info.file_at(i) - Url = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.quote(f.path) + Url = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote(f.path) files.append({ - 'name': localize_path(f.path), - 'size': f.size, - 'offset': f.offset, - 'media_type': media_types and detect_media_type(f.path.decode(chardet.detect(f.path)['encoding'])) or '', - 'download': 0, - 'progress': 0.0, - 'save_path': '', - 'url': Url - }) - if files: + 'name': localize_path(f.path), + 'size': f.size, + 'offset': f.offset, + 'media_type': media_types is not None and detect_media_type(f.path) or '', + 'download': 0, + 'progress': 0.0, + 'save_path': '', + 'url': Url + }) + if len(files) > 0: res = [FileStatus(index=index, **f) for index, f in enumerate(files)] if media_types is not None: - res = filter(lambda fs: fs.media_type in media_types, res) + res = [fs for fs in res if fs.media_type in media_types] return res def file_status(self, file_index, timeout=10): @@ -339,7 +348,7 @@ class Engine: return FileStatus(**filestatus) except: raise Error("Requested file index (%d) is invalid" % (file_index,), Error.INVALID_FILE_INDEX, - file_index=file_index) + file_index=file_index) def peers(self, timeout=10): """ @@ -355,6 +364,7 @@ class Engine: def is_alive(self): return self.pyrrent2http_loop.is_alive() + def wait_on_close(self, wait_timeout=10): """ By default, close() method sends shutdown command to pyrrent2http, stops logging and returns immediately, not diff --git a/lib/pyrrent2http/mimetypes.py b/lib/pyrrent2http/mimetypes.py deleted file mode 100644 index 94b548a..0000000 --- a/lib/pyrrent2http/mimetypes.py +++ /dev/null @@ -1,613 +0,0 @@ -"""Guess the MIME type of a file. - -This module defines two useful functions: - -guess_type(url, strict=1) -- guess the MIME type and encoding of a URL. - -guess_extension(type, strict=1) -- guess the extension for a given MIME type. - -It also contains the following, for tuning the behavior: - -Data: - -knownfiles -- list of files to parse -inited -- flag set when init() has been called -suffix_map -- dictionary mapping suffixes to suffixes -encodings_map -- dictionary mapping suffixes to encodings -types_map -- dictionary mapping suffixes to types - -Functions: - -init([files]) -- parse a list of files, default knownfiles (on Windows, the - default values are taken from the registry) -read_mime_types(file) -- parse one file, return a dictionary or None -""" - -import os -import sys -import posixpath -import urllib -try: - import _winreg -except ImportError: - _winreg = None - -__all__ = [ - "guess_type","guess_extension","guess_all_extensions", - "add_type","read_mime_types","init" -] - -knownfiles = [ - "/etc/mime.types", - "/etc/httpd/mime.types", # Mac OS X - "/etc/httpd/conf/mime.types", # Apache - "/etc/apache/mime.types", # Apache 1 - "/etc/apache2/mime.types", # Apache 2 - "/usr/local/etc/httpd/conf/mime.types", - "/usr/local/lib/netscape/mime.types", - "/usr/local/etc/httpd/conf/mime.types", # Apache 1.2 - "/usr/local/etc/mime.types", # Apache 1.3 - ] - -inited = False -_db = None - - -class MimeTypes: - """MIME-types datastore. - - This datastore can handle information from mime.types-style files - and supports basic determination of MIME type from a filename or - URL, and can guess a reasonable extension given a MIME type. - """ - - def __init__(self, filenames=(), strict=True): - if not inited: - init() - self.encodings_map = encodings_map.copy() - self.suffix_map = suffix_map.copy() - self.types_map = ({}, {}) # dict for (non-strict, strict) - self.types_map_inv = ({}, {}) - for (ext, type) in types_map.items(): - self.add_type(type, ext, True) - for (ext, type) in common_types.items(): - self.add_type(type, ext, False) - for name in filenames: - self.read(name, strict) - - def add_type(self, type, ext, strict=True): - """Add a mapping between a type and an extension. - - When the extension is already known, the new - type will replace the old one. When the type - is already known the extension will be added - to the list of known extensions. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - self.types_map[strict][ext] = type - exts = self.types_map_inv[strict].setdefault(type, []) - if ext not in exts: - exts.append(ext) - - def guess_type(self, url, strict=True): - """Guess the type of a file based on its URL. - - Return value is a tuple (type, encoding) where type is None if - the type can't be guessed (no or unknown suffix) or a string - of the form type/subtype, usable for a MIME Content-type - header; and encoding is None for no encoding or the name of - the program used to encode (e.g. compress or gzip). The - mappings are table driven. Encoding suffixes are case - sensitive; type suffixes are first tried case sensitive, then - case insensitive. - - The suffixes .tgz, .taz and .tz (case sensitive!) are all - mapped to '.tar.gz'. (This is table-driven too, using the - dictionary suffix_map.) - - Optional `strict' argument when False adds a bunch of commonly found, - but non-standard types. - """ - scheme, url = urllib.splittype(url) - if scheme == 'data': - # syntax of data URLs: - # dataurl := "data:" [ mediatype ] [ ";base64" ] "," data - # mediatype := [ type "/" subtype ] *( ";" parameter ) - # data := *urlchar - # parameter := attribute "=" value - # type/subtype defaults to "text/plain" - comma = url.find(',') - if comma < 0: - # bad data URL - return None, None - semi = url.find(';', 0, comma) - if semi >= 0: - type = url[:semi] - else: - type = url[:comma] - if '=' in type or '/' not in type: - type = 'text/plain' - return type, None # never compressed, so encoding is None - base, ext = posixpath.splitext(url) - while ext in self.suffix_map: - base, ext = posixpath.splitext(base + self.suffix_map[ext]) - if ext in self.encodings_map: - encoding = self.encodings_map[ext] - base, ext = posixpath.splitext(base) - else: - encoding = None - types_map = self.types_map[True] - if ext in types_map: - return types_map[ext], encoding - elif ext.lower() in types_map: - return types_map[ext.lower()], encoding - elif strict: - return None, encoding - types_map = self.types_map[False] - if ext in types_map: - return types_map[ext], encoding - elif ext.lower() in types_map: - return types_map[ext.lower()], encoding - else: - return None, encoding - - def guess_all_extensions(self, type, strict=True): - """Guess the extensions for a file based on its MIME type. - - Return value is a list of strings giving the possible filename - extensions, including the leading dot ('.'). The extension is not - guaranteed to have been associated with any particular data stream, - but would be mapped to the MIME type `type' by guess_type(). - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - type = type.lower() - extensions = self.types_map_inv[True].get(type, []) - if not strict: - for ext in self.types_map_inv[False].get(type, []): - if ext not in extensions: - extensions.append(ext) - return extensions - - def guess_extension(self, type, strict=True): - """Guess the extension for a file based on its MIME type. - - Return value is a string giving a filename extension, - including the leading dot ('.'). The extension is not - guaranteed to have been associated with any particular data - stream, but would be mapped to the MIME type `type' by - guess_type(). If no extension can be guessed for `type', None - is returned. - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - extensions = self.guess_all_extensions(type, strict) - if not extensions: - return None - return extensions[0] - - def read(self, filename, strict=True): - """ - Read a single mime.types-format file, specified by pathname. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - with open(filename) as fp: - self.readfp(fp, strict) - - def readfp(self, fp, strict=True): - """ - Read a single mime.types-format file. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - while 1: - line = fp.readline() - if not line: - break - words = line.split() - for i in range(len(words)): - if words[i][0] == '#': - del words[i:] - break - if not words: - continue - type, suffixes = words[0], words[1:] - for suff in suffixes: - self.add_type(type, '.' + suff, strict) - - def read_windows_registry(self, strict=True): - """ - Load the MIME types database from Windows registry. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - - # Windows only - if not _winreg: - return - - def enum_types(mimedb): - i = 0 - while True: - try: - ctype = _winreg.EnumKey(mimedb, i) - except EnvironmentError: - break - try: - ctype = ctype#.encode(default_encoding) # omit in 3.x! - except UnicodeEncodeError: - pass - else: - yield ctype - i += 1 - - default_encoding = sys.getdefaultencoding() - with _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT, - r'MIME\Database\Content Type') as mimedb: - for ctype in enum_types(mimedb): - try: - with _winreg.OpenKey(mimedb, ctype) as key: - suffix, datatype = _winreg.QueryValueEx(key, - 'Extension') - except EnvironmentError: - continue - if datatype != _winreg.REG_SZ: - continue - try: - suffix = suffix.encode(default_encoding) # omit in 3.x! - except UnicodeEncodeError: - continue - self.add_type(ctype, suffix, strict) - - -def guess_type(url, strict=True): - """Guess the type of a file based on its URL. - - Return value is a tuple (type, encoding) where type is None if the - type can't be guessed (no or unknown suffix) or a string of the - form type/subtype, usable for a MIME Content-type header; and - encoding is None for no encoding or the name of the program used - to encode (e.g. compress or gzip). The mappings are table - driven. Encoding suffixes are case sensitive; type suffixes are - first tried case sensitive, then case insensitive. - - The suffixes .tgz, .taz and .tz (case sensitive!) are all mapped - to ".tar.gz". (This is table-driven too, using the dictionary - suffix_map). - - Optional `strict' argument when false adds a bunch of commonly found, but - non-standard types. - """ - if _db is None: - init() - return _db.guess_type(url, strict) - - -def guess_all_extensions(type, strict=True): - """Guess the extensions for a file based on its MIME type. - - Return value is a list of strings giving the possible filename - extensions, including the leading dot ('.'). The extension is not - guaranteed to have been associated with any particular data - stream, but would be mapped to the MIME type `type' by - guess_type(). If no extension can be guessed for `type', None - is returned. - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - if _db is None: - init() - return _db.guess_all_extensions(type, strict) - -def guess_extension(type, strict=True): - """Guess the extension for a file based on its MIME type. - - Return value is a string giving a filename extension, including the - leading dot ('.'). The extension is not guaranteed to have been - associated with any particular data stream, but would be mapped to the - MIME type `type' by guess_type(). If no extension can be guessed for - `type', None is returned. - - Optional `strict' argument when false adds a bunch of commonly found, - but non-standard types. - """ - if _db is None: - init() - return _db.guess_extension(type, strict) - -def add_type(type, ext, strict=True): - """Add a mapping between a type and an extension. - - When the extension is already known, the new - type will replace the old one. When the type - is already known the extension will be added - to the list of known extensions. - - If strict is true, information will be added to - list of standard types, else to the list of non-standard - types. - """ - if _db is None: - init() - return _db.add_type(type, ext, strict) - - -def init(files=None): - global suffix_map, types_map, encodings_map, common_types - global inited, _db - inited = True # so that MimeTypes.__init__() doesn't call us again - db = MimeTypes() - if files is None: - if _winreg: - db.read_windows_registry() - files = knownfiles - for file in files: - if os.path.isfile(file): - db.read(file) - encodings_map = db.encodings_map - suffix_map = db.suffix_map - types_map = db.types_map[True] - common_types = db.types_map[False] - # Make the DB a global variable now that it is fully initialized - _db = db - - -def read_mime_types(file): - try: - f = open(file) - except IOError: - return None - db = MimeTypes() - db.readfp(f, True) - return db.types_map[True] - - -def _default_mime_types(): - global suffix_map - global encodings_map - global types_map - global common_types - - suffix_map = { - '.tgz': '.tar.gz', - '.taz': '.tar.gz', - '.tz': '.tar.gz', - '.tbz2': '.tar.bz2', - } - - encodings_map = { - '.gz': 'gzip', - '.Z': 'compress', - '.bz2': 'bzip2', - } - - # Before adding new types, make sure they are either registered with IANA, - # at http://www.isi.edu/in-notes/iana/assignments/media-types - # or extensions, i.e. using the x- prefix - - # If you add to these, please keep them sorted! - types_map = { - '.3gp' : 'video/3gp', - '.a' : 'application/octet-stream', - '.ai' : 'application/postscript', - '.aif' : 'audio/x-aiff', - '.aifc' : 'audio/x-aiff', - '.aiff' : 'audio/x-aiff', - '.asf' : 'video/x-ms-asf', - '.asx' : 'video/x-ms-asf', - '.au' : 'audio/basic', - '.avi' : 'video/x-msvideo', - '.axv' : 'video/annodex', - '.bat' : 'text/plain', - '.bcpio' : 'application/x-bcpio', - '.bin' : 'application/octet-stream', - '.bmp' : 'image/x-ms-bmp', - '.c' : 'text/plain', - # Duplicates :( - '.cdf' : 'application/x-cdf', - '.cdf' : 'application/x-netcdf', - '.cpio' : 'application/x-cpio', - '.csh' : 'application/x-csh', - '.css' : 'text/css', - '.dif' : 'video/dv', - '.dl' : 'video/dl', - '.dll' : 'application/octet-stream', - '.dv' : 'video/dv', - '.doc' : 'application/msword', - '.dot' : 'application/msword', - '.dvi' : 'application/x-dvi', - '.eml' : 'message/rfc822', - '.eps' : 'application/postscript', - '.etx' : 'text/x-setext', - '.exe' : 'application/octet-stream', - '.fli' : 'video/fli', - '.flv' : 'video/x-flv', - '.gif' : 'image/gif', - '.gl' : 'video/gl', - '.gtar' : 'application/x-gtar', - '.h' : 'text/plain', - '.hdf' : 'application/x-hdf', - '.htm' : 'text/html', - '.html' : 'text/html', - '.ief' : 'image/ief', - '.jpe' : 'image/jpeg', - '.jpeg' : 'image/jpeg', - '.jpg' : 'image/jpeg', - '.js' : 'application/x-javascript', - '.ksh' : 'text/plain', - '.latex' : 'application/x-latex', - '.lsf' : 'video/x-la-lsf', - '.lsx' : 'video/x-la-lsf', - '.m1v' : 'video/mpeg', - '.man' : 'application/x-troff-man', - '.me' : 'application/x-troff-me', - '.mht' : 'message/rfc822', - '.mhtml' : 'message/rfc822', - '.mif' : 'application/x-mif', - '.mng' : 'video/x-mng', - '.movie' : 'video/x-sgi-movie', - '.mp2' : 'audio/mpeg', - '.mp3' : 'audio/mpeg', - '.mp4' : 'video/mp4', - '.mpa' : 'video/mpeg', - '.mpe' : 'video/mpeg', - '.mpeg' : 'video/mpeg', - '.mpg' : 'video/mpeg', - '.mpv' : 'video/matroska', - '.mkv' : 'video/matroska', - '.mov' : 'video/quicktime', - '.ms' : 'application/x-troff-ms', - '.nc' : 'application/x-netcdf', - '.nws' : 'message/rfc822', - '.o' : 'application/octet-stream', - '.obj' : 'application/octet-stream', - '.oda' : 'application/oda', - '.ogv' : 'video/ogg', - '.p12' : 'application/x-pkcs12', - '.p7c' : 'application/pkcs7-mime', - '.pbm' : 'image/x-portable-bitmap', - '.pdf' : 'application/pdf', - '.pfx' : 'application/x-pkcs12', - '.pgm' : 'image/x-portable-graymap', - '.pl' : 'text/plain', - '.png' : 'image/png', - '.pnm' : 'image/x-portable-anymap', - '.pot' : 'application/vnd.ms-powerpoint', - '.ppa' : 'application/vnd.ms-powerpoint', - '.ppm' : 'image/x-portable-pixmap', - '.pps' : 'application/vnd.ms-powerpoint', - '.ppt' : 'application/vnd.ms-powerpoint', - '.ps' : 'application/postscript', - '.pwz' : 'application/vnd.ms-powerpoint', - '.py' : 'text/x-python', - '.pyc' : 'application/x-python-code', - '.pyo' : 'application/x-python-code', - '.qt' : 'video/quicktime', - '.ra' : 'audio/x-pn-realaudio', - '.ram' : 'application/x-pn-realaudio', - '.ras' : 'image/x-cmu-raster', - '.rdf' : 'application/xml', - '.rgb' : 'image/x-rgb', - '.roff' : 'application/x-troff', - '.rtx' : 'text/richtext', - '.sgm' : 'text/x-sgml', - '.sgml' : 'text/x-sgml', - '.sh' : 'application/x-sh', - '.shar' : 'application/x-shar', - '.snd' : 'audio/basic', - '.so' : 'application/octet-stream', - '.src' : 'application/x-wais-source', - '.sv4cpio': 'application/x-sv4cpio', - '.sv4crc' : 'application/x-sv4crc', - '.swf' : 'application/x-shockwave-flash', - '.t' : 'application/x-troff', - '.tar' : 'application/x-tar', - '.tcl' : 'application/x-tcl', - '.tex' : 'application/x-tex', - '.texi' : 'application/x-texinfo', - '.texinfo': 'application/x-texinfo', - '.tif' : 'image/tiff', - '.tiff' : 'image/tiff', - '.tr' : 'application/x-troff', - '.ts' : 'video/MP2T', - '.tsv' : 'text/tab-separated-values', - '.txt' : 'text/plain', - '.ustar' : 'application/x-ustar', - '.vcf' : 'text/x-vcard', - '.wav' : 'audio/x-wav', - '.webm' : 'video/webm', - '.wiz' : 'application/msword', - '.wm' : 'video/x-ms-wm', - '.wmv' : 'video/x-ms-wmv', - '.wmx' : 'video/x-ms-wmx', - '.wvx' : 'video/x-ms-wvx', - '.wsdl' : 'application/xml', - '.xbm' : 'image/x-xbitmap', - '.xlb' : 'application/vnd.ms-excel', - # Duplicates :( - '.xls' : 'application/excel', - '.xls' : 'application/vnd.ms-excel', - '.xml' : 'text/xml', - '.xpdl' : 'application/xml', - '.xpm' : 'image/x-xpixmap', - '.xsl' : 'application/xml', - '.xwd' : 'image/x-xwindowdump', - '.zip' : 'application/zip', - } - - # These are non-standard types, commonly found in the wild. They will - # only match if strict=0 flag is given to the API methods. - - # Please sort these too - common_types = { - '.jpg' : 'image/jpg', - '.mid' : 'audio/midi', - '.midi': 'audio/midi', - '.pct' : 'image/pict', - '.pic' : 'image/pict', - '.pict': 'image/pict', - '.rtf' : 'application/rtf', - '.xul' : 'text/xul' - } - - -_default_mime_types() - - -if __name__ == '__main__': - import getopt - - USAGE = """\ -Usage: mimetypes.py [options] type - -Options: - --help / -h -- print this message and exit - --lenient / -l -- additionally search of some common, but non-standard - types. - --extension / -e -- guess extension instead of type - -More than one type argument may be given. -""" - - def usage(code, msg=''): - print USAGE - if msg: print msg - sys.exit(code) - - try: - opts, args = getopt.getopt(sys.argv[1:], 'hle', - ['help', 'lenient', 'extension']) - except getopt.error, msg: - usage(1, msg) - - strict = 1 - extension = 0 - for opt, arg in opts: - if opt in ('-h', '--help'): - usage(0) - elif opt in ('-l', '--lenient'): - strict = 0 - elif opt in ('-e', '--extension'): - extension = 1 - for gtype in args: - if extension: - guess = guess_extension(gtype, strict) - if not guess: print "I don't know anything about type", gtype - else: print guess - else: - guess, encoding = guess_type(gtype, strict) - if not guess: print "I don't know anything about type", gtype - else: print 'type:', guess, 'encoding:', encoding diff --git a/lib/pyrrent2http/pyrrent2http.py b/lib/pyrrent2http/pyrrent2http.py index 1ab53da..eafd24a 100755 --- a/lib/pyrrent2http/pyrrent2http.py +++ b/lib/pyrrent2http/pyrrent2http.py @@ -4,12 +4,13 @@ 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, e: - print('Error importing python_libtorrent.Exception: %s' %(str(e),)) + + 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 + import libtorrent as lt # @UnresolvedImport except Exception as e: strerror = e.args print(strerror) @@ -17,17 +18,16 @@ except Exception, e: from random import SystemRandom import time -import urllib -import BaseHTTPServer -import SocketServer +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 - - +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] @@ -41,15 +41,16 @@ if os.getenv('ANDROID_ROOT'): if not hasattr(os, 'getppid'): import ctypes - TH32CS_SNAPPROCESS = 0x02L + TH32CS_SNAPPROCESS = 0x02 CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable - GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @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), @@ -65,9 +66,11 @@ if not hasattr(os, 'getppid'): ("szExeFile", ctypes.c_wchar * MAX_PATH) ] + Process32First = _kernel32dll.Process32FirstW Process32Next = _kernel32dll.Process32NextW + def getppid(): ''' :return: The pid of the parent of this process. @@ -93,27 +96,31 @@ if not hasattr(os, 'getppid'): return result + os.getppid = getppid ################################################################################# AVOID_HTTP_SERVER_EXCEPTION_OUTPUT = True VERSION = "0.6.0" -#USER_AGENT = "pyrrent2http/" + VERSION + " libtorrent/" + lt.version +# 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'} +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._timer = None + self.interval = interval self.is_running = False self.start() + @property def true(self): if self.tick: @@ -137,23 +144,25 @@ class Ticker(object): 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 + 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 = self.name.decode(chardet.detect(self.name)['encoding']) + 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 @@ -162,11 +171,13 @@ class TorrentFile(object): 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 @@ -180,35 +191,44 @@ class TorrentFile(object): 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 # Лучшее враг хорошего + 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)): + 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) @@ -217,9 +237,10 @@ class TorrentFile(object): 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 = 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,)) @@ -231,6 +252,7 @@ class TorrentFile(object): else: self.filePtr.close() self.filePtr = None + def ShowPieces(self): pieces = self.tfs.handle.status().pieces str_ = '' @@ -240,6 +262,7 @@ class TorrentFile(object): else: str_ += "#" self.log(str_) + def Read(self, buf): filePtr = self.__fileptr_() if filePtr is None: @@ -250,7 +273,7 @@ class TorrentFile(object): readOffset = self.readOffset() startPiece, _ = self.pieceFromOffset(readOffset) endPiece, _ = self.pieceFromOffset(readOffset + toRead) - for i in range(startPiece, endPiece + 1): + for i in range(startPiece, endPiece + 1): if not self.waitForPiece(i): raise IOError if os.getenv('ANDROID_ROOT'): @@ -258,6 +281,7 @@ class TorrentFile(object): else: read = filePtr.readinto(buf) return read + def Seek(self, offset, whence): filePtr = self.__fileptr_() if filePtr is None: return @@ -270,21 +294,23 @@ class TorrentFile(object): 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 + 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 @@ -296,8 +322,9 @@ class TorrentFS(object): num_files = self.info.num_files() for i in range(num_files): self.setPriority(i, 0) + def file(self, index): - for name in self.files.keys(): + for name in list(self.files.keys()): if self.files[name].index == index: return self.files[name] file_ = self.__file_at_(index) @@ -311,22 +338,27 @@ class TorrentFS(object): 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_) + 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) @@ -334,19 +366,24 @@ class TorrentFS(object): 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 self.files.keys(): + 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) + 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_ = [] @@ -354,29 +391,33 @@ class TorrentFS(object): file_ = self.__file_at_(i) file_.downloaded = self.getFileDownloadedBytes(i) if file_.size > 0: - file_.progress = float(file_.downloaded)/float(file_.size) + 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 - ) + 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: @@ -388,6 +429,7 @@ class TorrentFS(object): break if not found: self.setPriority(index, 0) + def OpenFile(self, name): try: tf = self.FileByName(name) @@ -404,25 +446,28 @@ class TorrentFS(object): self.files[tf.name] = tf self.checkPriorities() return tf - + + ############################################################# -class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): +class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): def handle_error(self, *args, **kwargs): '''Обходим злосчастный "Broken Pipe" и прочие трейсы''' if not AVOID_HTTP_SERVER_EXCEPTION_OUTPUT: - BaseHTTPServer.HTTPServer.handle_error(self, *args, **kwargs) + http.server.HTTPServer.handle_error(self, *args, **kwargs) + def HttpHandlerFactory(): - class HttpHandler(BaseHTTPServer.BaseHTTPRequestHandler): + class HttpHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): - #print ('---Headers---\n%s\n' % (self.headers,)) - #print ('---Request---\n%s\n' % (self.path,)) + # 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: @@ -454,15 +499,16 @@ def HttpHandlerFactory(): total += chunk start_range += chunk f.Close() + def send_head(self): - fname = urllib.unquote(self.path.lstrip('/files/')) + fname = urllib.parse.unquote(self.path.lstrip('/files/')) try: - f = self.server.root_obj.TorrentFS.Open(fname) + 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 VIDEO_EXTS.keys())and VIDEO_EXTS[ext] or 'application/octet-stream' + 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: @@ -489,28 +535,32 @@ def HttpHandlerFactory(): 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) + # print "Sending Bytes ",start_range, " to ", end_range, "...\n" + return f, start_range, end_range + # Вырубаем access-log - def log_message(self, format, *args): + 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): + + 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 @@ -563,7 +613,7 @@ class Pyrrent2http(object): 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) @@ -577,7 +627,7 @@ class Pyrrent2http(object): 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: @@ -591,7 +641,7 @@ class Pyrrent2http(object): 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') @@ -604,11 +654,11 @@ class Pyrrent2http(object): # self.torrentHandle.set_max_connections(60) if self.config.trackers != '': - trackers = self.config.trackers.split(',') - startTier = 256 - len(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,) ) + logging.info('Adding tracker: %s' % (tracker,)) self.torrentHandle.add_tracker({'url': tracker}) if self.config.enableScrape: logging.info('Sending scrape request to tracker') @@ -623,8 +673,8 @@ class Pyrrent2http(object): except Exception as e: logging.error(e.args) name = self.TorrentFS.info.name() - self.torrent_name = name.decode(chardet.detect(name)['encoding']) - + self.torrent_name = name + def startHTTP(self): logging.info('Starting HTTP Server...') handler = HttpHandlerFactory() @@ -635,9 +685,9 @@ class Pyrrent2http(object): 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 = threading.Thread(target=self.httpListener.serve_forever) self.listener_thread.start() - + def startServices(self): if self.config.enableDHT: logging.info('Starting DHT...') @@ -651,19 +701,19 @@ class Pyrrent2http(object): 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 | + 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 @@ -681,19 +731,19 @@ class Pyrrent2http(object): ### Непонятно, как заставить использовать прокси только для подключения к трекеру? if self.config.proxy is not None: ps = lt.proxy_settings() - #peer_ps = lt.proxy_settings() - #peer_ps.type = lt.proxy_type.none + # 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_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: @@ -704,7 +754,7 @@ class Pyrrent2http(object): logging.error(strerror) else: self.session.load_state(lt.bdecode(bytes__)) - + rand = SystemRandom(time.time()) portLower = self.config.listenPort if self.config.randomPort: @@ -716,7 +766,7 @@ class Pyrrent2http(object): strerror = e.args logging.error(strerror) raise - + settings = self.session.get_settings() if self.config.userAgent != '': settings['user_agent'] = self.config.userAgent @@ -731,7 +781,7 @@ class Pyrrent2http(object): 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: @@ -757,70 +807,74 @@ class Pyrrent2http(object): 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 - } + '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 self.TorrentFS.files.keys() if self.TorrentFS.files[n].index == index] + 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.quote(name) + 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 - } + '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 - } + '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: @@ -831,6 +885,7 @@ class Pyrrent2http(object): alert = self.session.pop_alert() if type(alert) == alert_type: return alert + def loop(self): self.saveResumeDataTicker = Ticker(5) time_start = time.time() @@ -859,16 +914,18 @@ class Pyrrent2http(object): except IOError as e: strerror = e.args logging.error(strerror) - def saveResumeData(self, async = False): + + 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: + if not async_: alert = self.waitForAlert(lt.save_resume_data_alert, 5) - if alert == None: + if alert is None: return False self.processSaveResumeDataAlert(alert) return True + def saveSessionState(self): if self.config.stateFile == '': return @@ -882,6 +939,7 @@ class Pyrrent2http(object): except IOError as e: strerror = e.args logging.error(strerror) + def removeFiles(self, files): for file in files: try: @@ -897,6 +955,7 @@ class Pyrrent2http(object): 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(): @@ -907,6 +966,7 @@ class Pyrrent2http(object): if os.path.exists(path): files.append(path) return files + def removeTorrent(self): files = [] flag = 0 @@ -922,6 +982,7 @@ class Pyrrent2http(object): 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 diff --git a/lib/pyrrent2http/structs.py b/lib/pyrrent2http/structs.py new file mode 100644 index 0000000..4701b56 --- /dev/null +++ b/lib/pyrrent2http/structs.py @@ -0,0 +1,22 @@ +class State: + QUEUED_FOR_CHECKING = 0 + CHECKING_FILES = 1 + DOWNLOADING_METADATA = 2 + DOWNLOADING = 3 + FINISHED = 4 + SEEDING = 5 + ALLOCATING = 6 + CHECKING_RESUME_DATA = 7 + + +class MediaType: + UNKNOWN = None + AUDIO = 'audio' + VIDEO = 'video' + SUBTITLES = 'subtitles' + + +class Encryption: + FORCED = 0 + ENABLED = 1 + DISABLED = 2 \ No newline at end of file diff --git a/lib/pyrrent2http/util.py b/lib/pyrrent2http/util.py index c624a49..18fbca0 100644 --- a/lib/pyrrent2http/util.py +++ b/lib/pyrrent2http/util.py @@ -1,31 +1,41 @@ -import sys -import socket -import chardet -import os -from . import MediaType import mimetypes -import urlparse, urllib +import os +import socket +import urllib.error +import urllib.parse +import urllib.parse +import urllib.request + +import chardet +import sys +import xbmc + +from .structs import MediaType SUBTITLES_FORMATS = ['.aqt', '.gsub', '.jss', '.sub', '.ttxt', '.pjs', '.psb', '.rt', '.smi', '.stl', - '.ssf', '.srt', '.ssa', '.ass', '.usf', '.idx'] + '.ssf', '.srt', '.ssa', '.ass', '.usf', '.idx'] + class Struct(dict): def __getattr__(self, attr): return self[attr] + def __setattr__(self, attr, value): self[attr] = value + def uri2path(uri): if uri[1] == ':' and sys.platform.startswith('win'): uri = 'file:///' + uri - fileUri = urlparse.urlparse(uri) + fileUri = urllib.parse.urlparse(uri) if fileUri.scheme == 'file': uriPath = fileUri.path if uriPath != '' and sys.platform.startswith('win') and (os.path.sep == uriPath[0] or uriPath[0] == '/'): uriPath = uriPath[1:] - absPath = os.path.abspath(urllib.unquote(uriPath)) + absPath = os.path.abspath(urllib.parse.unquote(uriPath)) return localize_path(absPath) + def detect_media_type(name): ext = os.path.splitext(name)[1] if ext in SUBTITLES_FORMATS: @@ -41,24 +51,31 @@ def detect_media_type(name): return MediaType.VIDEO else: return MediaType.UNKNOWN + + def unicode_msg(tmpl, args): - msg = isinstance(tmpl, unicode) and tmpl or tmpl.decode(chardet.detect(tmpl)['encoding']) + msg = isinstance(tmpl, str) and tmpl or tmpl.decode(chardet.detect(tmpl)['encoding']) arg_ = [] for a in args: - arg_.append(isinstance(a, unicode) and a or a.decode(chardet.detect(a)['encoding'])) + arg_.append(isinstance(a, str) and a or a.decode(chardet.detect(a)['encoding'])) return msg % tuple(arg_) + def encode_msg(msg): - msg = isinstance(msg, unicode) and msg.encode(sys.getfilesystemencoding() != 'ascii' and sys.getfilesystemencoding() or 'utf-8') or msg + msg = isinstance(msg, str) and msg.encode( + sys.getfilesystemencoding() != 'ascii' and sys.getfilesystemencoding() or 'utf-8') or msg return msg - + def localize_path(path): - if not isinstance(path, unicode): path = path.decode(chardet.detect(path)['encoding']) - if not sys.platform.startswith('win'): - path = path.encode((sys.getfilesystemencoding() not in ('ascii', 'ANSI_X3.4-1968')) and sys.getfilesystemencoding() or 'utf-8') + if isinstance(path, bytes): + path = path.decode(chardet.detect(path)['encoding']) + # if not sys.platform.startswith('win'): + # path = path.encode( + # (sys.getfilesystemencoding() not in ('ascii', 'ANSI_X3.4-1968')) and sys.getfilesystemencoding() or 'utf-8') return path + def can_bind(host, port): """ Checks we can bind to specified host and port @@ -94,6 +111,6 @@ def find_free_port(host): def ensure_fs_encoding(string): - if isinstance(string, str): + if isinstance(string, bytes): string = string.decode('utf-8') return string.encode(sys.getfilesystemencoding() or 'utf-8')