рабочая версия плагина
This commit is contained in:
		
							parent
							
								
									5a640c4ae4
								
							
						
					
					
						commit
						ca2a815140
					
				| @ -1,8 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||||
| <addon id="script.module.pyrrent2http" name="pyrrent2http" version="1.1.1" provider-name="inpos"> | ||||
| <addon id="script.module.gorrent2http" name="gorrent2http" version="1.0.0" provider-name="inpos"> | ||||
|     <requires> | ||||
|         <import addon="xbmc.python" version="3.0.0"/> | ||||
|         <import	addon="script.module.libtorrent" version="1.2.0"/> | ||||
|         <import addon="script.module.chardet" /> | ||||
|     </requires> | ||||
|     <extension point="xbmc.python.module" library="lib"/> | ||||
| @ -17,5 +16,5 @@ | ||||
|         <description lang="ru">Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent.</description> | ||||
|         <description lang="en">Provides sequential torrent downloading for online streaming video and other media over HTTP.</description> | ||||
|         <email>inpos@yandex.ru</email> | ||||
|         <source>https://git.ukamnya.ru/ukamnya/script.module.pyrrent2http</source></extension> | ||||
|         <source>https://git.ukamnya.ru/ukamnya/script.module.gorrent2http</source></extension> | ||||
| </addon> | ||||
|  | ||||
| @ -5,30 +5,45 @@ import urllib.error | ||||
| import urllib.parse | ||||
| import urllib.request | ||||
| 
 | ||||
| import chardet | ||||
| import sys | ||||
| import time | ||||
| import xbmc | ||||
| import xbmcvfs | ||||
| from . import log as logging | ||||
| 
 | ||||
| from . import SessionStatus, FileStatus, PeerInfo | ||||
| from . import pyrrent2http | ||||
| from . import FileStatus, SessionStatus | ||||
| from .error import Error | ||||
| from .structs import Encryption | ||||
| from .util import can_bind, find_free_port, localize_path, uri2path, detect_media_type | ||||
| from .util import can_bind, find_free_port, localize_path, uri2path, detect_media_type, get_platform | ||||
| 
 | ||||
| platform = get_platform() | ||||
| dirname = os.path.join(xbmcvfs.translatePath('special://temp'), 'xbmcup', 'script.module.gorrent2http') | ||||
| dest_path = os.path.join(dirname, platform['system']) | ||||
| sys.path.insert(0, dest_path) | ||||
| 
 | ||||
| try: | ||||
|     from gorrent import gorrent as gt | ||||
| 
 | ||||
|     logging.info(f'Imported gorrent v{gt.Version()}') | ||||
| except Exception: | ||||
|     import traceback | ||||
| 
 | ||||
|     logging.error(f'Error importing gorrent. Exception: {traceback.format_exc()}') | ||||
|     raise | ||||
| 
 | ||||
| LOGGING = True | ||||
| 
 | ||||
| 
 | ||||
| class Engine: | ||||
|     """ | ||||
|     This is python binding class to pyrrent2http client. | ||||
|     This is python binding class to gorrent2http client. | ||||
|     """ | ||||
| 
 | ||||
|     def _log(self, message): | ||||
|         if self.logger: | ||||
|             self.logger(message) | ||||
|         else: | ||||
|             xbmc.log("[pyrrent2http] %s" % message) | ||||
|             xbmc.log("[gorrent2http] %s" % message) | ||||
| 
 | ||||
|     def __init__(self, uri=None, platform=None, download_path=".", | ||||
|                  bind_host='127.0.0.1', bind_port=5001, connections_limit=200, download_kbps=-1, upload_kbps=-1, | ||||
| @ -45,7 +60,6 @@ class Engine: | ||||
|         start() method. | ||||
| 
 | ||||
|         :param uri: Torrent URI (magnet://, file:// or http://) | ||||
|         :param binaries_path: Path to torrent2http binaries | ||||
|         :param platform: Object with two methods implemented: arch() and system() | ||||
|         :param download_path: Torrent download path | ||||
|         :param bind_host: Bind host of torrent2http | ||||
| @ -134,16 +148,12 @@ class Engine: | ||||
|         self.started = False | ||||
|         self.proxy = proxy | ||||
| 
 | ||||
|         self.message_logger = None | ||||
|         self.run_message_logger = True | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def _validate_save_path(path): | ||||
|         """ | ||||
|         Ensures download path can be accessed locally. | ||||
| 
 | ||||
|         :param path: Download path | ||||
|         :return: Translated path | ||||
|         """ | ||||
|         import xbmc | ||||
|         path = xbmc.translatePath(path) | ||||
|         path = xbmcvfs.translatePath(path) | ||||
|         if "://" in path: | ||||
|             if sys.platform.startswith('win') and path.lower().startswith("smb://"): | ||||
|                 path = path.replace("smb:", "").replace("/", "\\") | ||||
| @ -153,67 +163,16 @@ class Engine: | ||||
|             raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH) | ||||
|         return localize_path(path) | ||||
| 
 | ||||
|     def start(self, start_index=None): | ||||
|         """ | ||||
|         Starts pyrrent2http client with specified settings. If it can be started in startup_timeout seconds, exception | ||||
|         will be raised. | ||||
| 
 | ||||
|         :param start_index: File index to start download instantly, if not specified, downloading will be paused, until | ||||
|             any file requested | ||||
|         """ | ||||
| 
 | ||||
|     def start(self, start_index): | ||||
|         download_path = self._validate_save_path(self.download_path) | ||||
|         if not can_bind(self.bind_host, self.bind_port): | ||||
|             port = find_free_port(self.bind_host) | ||||
|             if port is False: | ||||
|                 raise Error("Can't find port to bind pyrrent2http", Error.BIND_ERROR) | ||||
|                 raise Error("Can't find port to bind gorrent2http", Error.BIND_ERROR) | ||||
|             self._log("Can't bind to %s:%s, so we found another port: %d" % (self.bind_host, self.bind_port, port)) | ||||
|             self.bind_port = port | ||||
| 
 | ||||
|         kwargs = { | ||||
|             'torrentConnectBoost': self.torrent_connect_boost, | ||||
|             'trackers': ",".join(self.trackers), | ||||
|             'proxy': self.proxy, | ||||
|             'resumeFile': self.resume_file, | ||||
|             'minReconnectTime': self.min_reconnect_time, | ||||
|             'enableUPNP': self.enable_upnp, | ||||
|             'showAllStats': self.log_stats, | ||||
|             'debugAlerts': self.debug_alerts, | ||||
|             'keepComplete': self.keep_complete, | ||||
|             'dhtRouters': ",".join(self.dht_routers), | ||||
|             'userAgent': self.user_agent, | ||||
|             'enableLSD': self.enable_lsd, | ||||
|             'uri': self.uri, | ||||
|             'randomPort': self.use_random_port, | ||||
|             'noSparseFile': self.no_sparse, | ||||
|             'maxUploadRate': self.upload_kbps, | ||||
|             'downloadPath': download_path, | ||||
|             'showOverallProgress': self.log_overall_progress, | ||||
|             'enableDHT': self.enable_dht, | ||||
|             'showFilesProgress': self.log_files_progress, | ||||
|             'requestTimeout': self.request_timeout, | ||||
|             'bindAddress': "%s:%s" % (self.bind_host, self.bind_port), | ||||
|             'maxDownloadRate': self.download_kbps, | ||||
|             'connectionSpeed': self.connection_speed, | ||||
|             'keepIncomplete': self.keep_incomplete, | ||||
|             'enableTCP': self.enable_tcp, | ||||
|             'listenPort': self.listen_port, | ||||
|             'keepFiles': self.keep_files, | ||||
|             'stateFile': self.state_file, | ||||
|             'peerConnectTimeout': self.peer_connect_timeout, | ||||
|             'maxFailCount': self.max_failcount, | ||||
|             'showPiecesProgress': self.log_pieces_progress, | ||||
|             'idleTimeout': self.max_idle_timeout, | ||||
|             # 'fileIndex': start_index, | ||||
|             'connectionsLimit': self.connections_limit, | ||||
|             'enableScrape': self.enable_scrape, | ||||
|             'enableUTP': self.enable_utp, | ||||
|             'encryption': self.encryption, | ||||
|             'enableNATPMP': self.enable_natpmp | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         self._log("Invoking pyrrent2http") | ||||
|         self._log("Invoking gorrent2http") | ||||
| 
 | ||||
|         class Logging(object): | ||||
|             def __init__(self, _log): | ||||
| @ -227,15 +186,34 @@ class Engine: | ||||
|                 if LOGGING: | ||||
|                     self._log('ERROR: %s' % (message,)) | ||||
| 
 | ||||
|         pyrrent2http.logging = Logging(self._log) | ||||
|         gt.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.start() | ||||
|         settings = gt.NewSettings() | ||||
|         settings.DownloadPath = download_path | ||||
|         settings.HttpBindHost = self.bind_host | ||||
|         settings.HttpBindPort = self.bind_port | ||||
|         settings.ListenPort = self.listen_port | ||||
|         settings.TorrentPath = uri2path(self.uri) | ||||
|         settings.MaxConnections = self.connections_limit | ||||
|         settings.Debug = self.debug_alerts | ||||
|         settings.KeepFiles = self.keep_files | ||||
|         settings.Proxy = len(self.proxy) > 0 and f'socks5://{self.proxy["host"]}:{self.proxy["port"]}' or '' | ||||
| 
 | ||||
|         self.engine = gt.NewEngine(settings) | ||||
|         def msg_logger(): | ||||
|             while self.run_message_logger: | ||||
|                 msg = self.engine.GetMsg() | ||||
|                 if msg not in ('__NO_MSG__', '__CLOSED__'): | ||||
|                     xbmc.log(f'-= GORRENT =-: {msg}') | ||||
|                 time.sleep(0.1) | ||||
|         self.message_logger = threading.Thread(target=msg_logger) | ||||
|         self.message_logger.start() | ||||
| 
 | ||||
|         self._log('starting torrent') | ||||
| 
 | ||||
|         self.engine.StartTorrent(start_index) | ||||
| 
 | ||||
|         self._log('waiting alive status set') | ||||
| 
 | ||||
|         start = time.time() | ||||
|         self.started = True | ||||
| @ -243,7 +221,7 @@ class Engine: | ||||
|         while (time.time() - start) < self.startup_timeout: | ||||
|             time.sleep(0.1) | ||||
|             if not self.is_alive(): | ||||
|                 raise Error("Can't start pyrrent2http, see log for details", Error.PROCESS_ERROR) | ||||
|                 raise Error("Can't start gorrent2http, see log for details", Error.PROCESS_ERROR) | ||||
|             try: | ||||
|                 # self.status(1) | ||||
|                 initialized = True | ||||
| @ -253,79 +231,77 @@ class Engine: | ||||
| 
 | ||||
|         if not initialized: | ||||
|             self.started = False | ||||
|             raise Error("Can't start pyrrent2http, time is out", Error.TIMEOUT) | ||||
|         self._log("pyrrent2http successfully started.") | ||||
| 
 | ||||
|     def activate_file(self, index): | ||||
|         self.pyrrent2http.TorrentFS.file(index) | ||||
|             raise Error("Can't start gorrent2http, time is out", Error.TIMEOUT) | ||||
|         self._log("gorrent2http successfully started.") | ||||
| 
 | ||||
|     def pause(self): | ||||
|         self.pyrrent2http.pause = True | ||||
|         pass | ||||
| 
 | ||||
|     def resume(self): | ||||
|         self.pyrrent2http.pause = False | ||||
|         pass | ||||
| 
 | ||||
|     def check_torrent_error(self, status=None): | ||||
|         """ | ||||
|         It is recommended to call this method periodically to check if any libtorrent errors occurred. | ||||
|         Usually libtorrent sets error if it can't download or parse torrent file by specified URI. | ||||
|         Note that pyrrent2http remains started after such error, so you need to shutdown it manually. | ||||
| 
 | ||||
|         :param status: Pass return of status() method if you don't want status() called twice | ||||
|         """ | ||||
|         if not status: | ||||
|             status = self.status() | ||||
|         if status.error: | ||||
|             raise Error("Torrent error: %s" % status.error, Error.TORRENT_ERROR, reason=status.error) | ||||
| 
 | ||||
|     def status(self, timeout=10): | ||||
|         """ | ||||
|         Returns libtorrent session status. See SessionStatus named tuple. | ||||
| 
 | ||||
|         :rtype : SessionStatus | ||||
|         :param timeout: pyrrent2http client request timeout | ||||
|         """ | ||||
|         status = self.pyrrent2http.Status() | ||||
|         status = SessionStatus(**status) | ||||
|         return status | ||||
| 
 | ||||
|     def list(self, media_types=None, timeout=10): | ||||
|         """ | ||||
|         Returns list of files in the torrent (see FileStatus named tuple). | ||||
|         Note that it will return None if torrent file is not loaded yet by pyrrent2http client, so you may need to call | ||||
|         this method periodically until results are returned. | ||||
| 
 | ||||
|         :param media_types: List of media types (see MediaType constants) | ||||
|         :param timeout: pyrrent2http client request timeout | ||||
|         :rtype : list of FileStatus | ||||
|         :return: List of files of specified media types or None if torrent is not loaded yet | ||||
|         """ | ||||
|         files = self.pyrrent2http.Ls()['files'] | ||||
|         if files: | ||||
|             res = [FileStatus(index=index, **f) for index, f in enumerate(files)] | ||||
|             if media_types is not None: | ||||
|                 res = [fs for fs in res if fs.media_type in media_types] | ||||
|             return res | ||||
|         stat = self.engine.Status() | ||||
|         stat_kw = { | ||||
|             'download_rate': stat.DownloadRate, | ||||
|             'upload_rate': stat.UploadRate, | ||||
|             'num_seeds': stat.Seeds, | ||||
|             'name': '', | ||||
|             'state': 0, | ||||
|             'state_str': '', | ||||
|             'error': '', | ||||
|             'progress': 0, | ||||
|             'total_download': 0, | ||||
|             'total_upload': 0, | ||||
|             'num_peers': 0, | ||||
|             'total_seeds': 0, | ||||
|             'total_peers': 0 | ||||
|         } | ||||
|         return SessionStatus(**stat_kw) | ||||
| 
 | ||||
|     def list_from_info(self, media_types=None): | ||||
|         try: | ||||
|             info = pyrrent2http.lt.torrent_info(uri2path(self.uri)) | ||||
|             info = gt.GetMetaFromFile(uri2path(self.uri)) | ||||
|         except: | ||||
|             import traceback | ||||
|             xbmc.log(f'info load exception: {traceback.format_exc()}') | ||||
|             return [] | ||||
|         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.parse.quote(f.path) | ||||
|         if len(info.Files) > 0: | ||||
|             for i in range(len(info.Files)): | ||||
|                 f = info.Files[i] | ||||
| 
 | ||||
|                 uri = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote( | ||||
|                     '/'.join(f.Path)) | ||||
|                 files.append({ | ||||
|                 '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 '', | ||||
|                     'name': localize_path('/'.join(f.Path)), | ||||
|                     'size': f.Length, | ||||
|                     'offset': i, | ||||
|                     'media_type': media_types is not None and detect_media_type(f.Path[-1]) or '', | ||||
|                     'download': 0, | ||||
|                     'progress': 0.0, | ||||
|                     'save_path': '', | ||||
|                 'url': Url | ||||
|                     'url': uri | ||||
|                 }) | ||||
|         else: | ||||
|             files.append({ | ||||
|                 'name': localize_path(info.Name), | ||||
|                 'size': info.Length, | ||||
|                 'offset': 0, | ||||
|                 'media_type': media_types is not None and detect_media_type(info.Name) or '', | ||||
|                 'download': 0, | ||||
|                 'progress': 0.0, | ||||
|                 'save_path': '', | ||||
|                 'url': 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote( | ||||
|                     info.Name) | ||||
|             }) | ||||
|         res = [] | ||||
|         if len(files) > 0: | ||||
|             res = [FileStatus(index=index, **f) for index, f in enumerate(files)] | ||||
|         if media_types is not None: | ||||
| @ -333,57 +309,34 @@ class Engine: | ||||
|         return res | ||||
| 
 | ||||
|     def file_status(self, file_index, timeout=10): | ||||
|         """ | ||||
|         Returns file in the torrent with specified index (see FileStatus named tuple) | ||||
|         Note that it will return None if torrent file is not loaded yet by pyrrent2http client, so you may need to call | ||||
|         this method periodically until results are returned. | ||||
| 
 | ||||
|         :param file_index: Requested file's index | ||||
|         :param timeout: pyrrent2http client request timeout | ||||
|         :return: File with specified index | ||||
|         :rtype: FileStatus | ||||
|         """ | ||||
|         filestatus = self.pyrrent2http.Ls(file_index) | ||||
|         try: | ||||
|             return FileStatus(**filestatus) | ||||
|             efs = self.engine.FileStatus(file_index) | ||||
|             fstat = { | ||||
|                 'name': localize_path('/'.join(efs.Name)), | ||||
|                 'progress': efs.Progress, | ||||
|                 'url': efs.Url, | ||||
|                 'save_path': '', | ||||
|                 'size': efs.Length, | ||||
|                 'offset': 0, | ||||
|                 'download': 0, | ||||
|                 'media_type': '' | ||||
|             } | ||||
|             return FileStatus(index=file_index, **fstat) | ||||
|         except: | ||||
|             raise Error("Requested file index (%d) is invalid" % (file_index,), Error.INVALID_FILE_INDEX, | ||||
|                         file_index=file_index) | ||||
| 
 | ||||
|     def peers(self, timeout=10): | ||||
|         """ | ||||
|         Returns list of peers connected (see PeerInfo named tuple). | ||||
| 
 | ||||
|         :param timeout: pyrrent2http client request timeout | ||||
|         :return: List of peers | ||||
|         :rtype: list of PeerInfo | ||||
|         """ | ||||
|         peers = self.pyrrent2http.Peers()['peers'] | ||||
|         if peers: | ||||
|             return [PeerInfo(**p) for p in peers] | ||||
| 
 | ||||
|     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 | ||||
|         waiting while pyrrent2http exits. It can be handy to wait pyrrent2http to view log messages during shutdown. | ||||
|         So call this method with reasonable timeout before calling close(). | ||||
| 
 | ||||
|         :param wait_timeout: Time in seconds to wait until pyrrent2http client shut down | ||||
|         """ | ||||
|         self.wait_on_close_timeout = wait_timeout | ||||
|         return self.engine.IsAlive() | ||||
| 
 | ||||
|     def close(self): | ||||
|         """ | ||||
|         Shuts down pyrrent2http and stops logging. If wait_on_close() was called earlier, it will wait until | ||||
|         pyrrent2http successfully exits. | ||||
|         """ | ||||
|         if self.is_alive(): | ||||
|             self._log("Shutting down pyrrent2http...") | ||||
|             self.pyrrent2http.shutdown() | ||||
|             self._log("Shutting down gorrent2http...") | ||||
|             self.engine.Stop() | ||||
|             finished = False | ||||
| 
 | ||||
|             self.wait_on_close_timeout = 10 | ||||
| 
 | ||||
|             if self.wait_on_close_timeout is not None: | ||||
|                 start = time.time() | ||||
|                 while (time.time() - start) < self.wait_on_close_timeout: | ||||
| @ -392,11 +345,12 @@ class Engine: | ||||
|                         finished = True | ||||
|                         break | ||||
|                 if not finished: | ||||
|                     self._log("PANIC: Timeout occurred while shutting down pyrrent2http thread") | ||||
|                     self._log("PANIC: Timeout occurred while shutting down gorrent2http thread") | ||||
|                 else: | ||||
|                     self._log("pyrrent2http successfully shut down.") | ||||
|                     self._log("gorrent2http successfully shut down.") | ||||
|                 self.wait_on_close_timeout = None | ||||
|             self._log("pyrrent2http successfully shut down.") | ||||
|             self._log("gorrent2http successfully shut down.") | ||||
|         if self.message_logger is not None and self.message_logger.is_alive(): | ||||
|             self.run_message_logger = False | ||||
|         self.message_logger = None | ||||
|         self.started = False | ||||
|         self.logpipe = None | ||||
|         self.process = None | ||||
| @ -1,30 +1,38 @@ | ||||
| # -*- 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 io | ||||
| import os | ||||
| import socketserver | ||||
| import threading | ||||
| import io | ||||
| import urllib.error | ||||
| import urllib.parse | ||||
| import urllib.request | ||||
| from random import SystemRandom | ||||
| from . import log as logging | ||||
| 
 | ||||
| import chardet | ||||
| import sys | ||||
| import time | ||||
| import xbmc | ||||
| import xbmcvfs | ||||
| 
 | ||||
| from . import util | ||||
| from .util import localize_path, Struct, detect_media_type, uri2path, encode_msg | ||||
| 
 | ||||
| 
 | ||||
| platform = util.get_platform() | ||||
| dirname = os.path.join(xbmcvfs.translatePath('special://temp'), 'xbmcup', 'script.module.gorrent2http') | ||||
| dest_path = os.path.join(dirname, platform['system']) | ||||
| sys.path.insert(0, dest_path) | ||||
| 
 | ||||
| try: | ||||
|     from gorrent import gorrent as gt | ||||
|     logging.info(f'Imported gorrent v{gt.Version()}') | ||||
| except Exception: | ||||
|     import traceback | ||||
|     logging.error(f'Error importing gorrent. Exception: {traceback.format_exc()}') | ||||
|     raise | ||||
| 
 | ||||
| if os.getenv('ANDROID_ROOT'): | ||||
|     from ctypes import * | ||||
| 
 | ||||
| @ -72,9 +80,9 @@ if not hasattr(os, 'getppid'): | ||||
| 
 | ||||
| 
 | ||||
|     def getppid(): | ||||
|         ''' | ||||
|         """ | ||||
|         :return: The pid of the parent of this process. | ||||
|         ''' | ||||
|         """ | ||||
|         pe = PROCESSENTRY32() | ||||
|         pe.dwSize = ctypes.sizeof(PROCESSENTRY32) | ||||
|         mypid = GetCurrentProcessId() | ||||
| @ -162,7 +170,8 @@ class TorrentFile(object): | ||||
|         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.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 | ||||
| @ -183,7 +192,7 @@ class TorrentFile(object): | ||||
|             return None | ||||
|         if self.filePtr is None: | ||||
|             while not os.path.exists(self.save_path): | ||||
|                 logging.info('Waiting for file: %s' % (self.save_path,)) | ||||
|                 xbmc.log('INFO: Waiting for file: %s' % (self.save_path,)) | ||||
|                 self.tfs.handle.flush_cache() | ||||
|                 time.sleep(0.5) | ||||
|             if os.getenv('ANDROID_ROOT'): | ||||
| @ -194,7 +203,7 @@ class TorrentFile(object): | ||||
| 
 | ||||
|     def log(self, message): | ||||
|         fnum = self.tfs.openedFiles.index(self) | ||||
|         logging.info("[Thread No.%d] %s\n" % (fnum, message)) | ||||
|         xbmc.log("INFO: [Thread No.%d] %s\n" % (fnum, message)) | ||||
| 
 | ||||
|     def Pieces(self): | ||||
|         startPiece, _ = self.pieceFromOffset(1) | ||||
| @ -545,10 +554,10 @@ def HttpHandlerFactory(): | ||||
|     return HttpHandler | ||||
| 
 | ||||
| 
 | ||||
| class Pyrrent2http(object): | ||||
| class Gorrent2http(object): | ||||
|     pause = False | ||||
| 
 | ||||
|     def __init__(self, uri='', bindAddress='localhost:5001', downloadPath='.', | ||||
|     def __init__(self, uri='', bind_address='localhost:5001', download_path='.', | ||||
|                  idleTimeout=-1, keepComplete=False, | ||||
|                  keepIncomplete=False, keepFiles=False, showAllStats=False, | ||||
|                  showOverallProgress=False, showFilesProgress=False, | ||||
| @ -568,8 +577,8 @@ class Pyrrent2http(object): | ||||
| 
 | ||||
|         self.config = Struct() | ||||
|         self.config.uri = uri | ||||
|         self.config.bindAddress = bindAddress | ||||
|         self.config.downloadPath = downloadPath | ||||
|         self.config.bindAddress = bind_address | ||||
|         self.config.downloadPath = download_path | ||||
|         self.config.idleTimeout = idleTimeout | ||||
|         self.config.keepComplete = keepComplete | ||||
|         self.config.keepIncomplete = keepIncomplete | ||||
| @ -618,7 +627,7 @@ class Pyrrent2http(object): | ||||
|         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())) | ||||
|             torrent_info = gt.torrent_info(gt.bdecode(open(absPath, 'rb').read())) | ||||
|         except Exception as e: | ||||
|             strerror = e.args | ||||
|             logging.error('Build torrent params error is (%s)' % (strerror,)) | ||||
| @ -639,7 +648,7 @@ class Pyrrent2http(object): | ||||
|                 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 | ||||
|             torrentParams["storage_mode"] = gt.storage_mode_t.storage_mode_allocate | ||||
|         return torrentParams | ||||
| 
 | ||||
|     def addTorrent(self): | ||||
| @ -704,14 +713,14 @@ class Pyrrent2http(object): | ||||
| 
 | ||||
|     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) | ||||
|         self.session = gt.session(gt.fingerprint('LT', gt.version_major, gt.version_minor, 0, 0), | ||||
|                                   flags=int(gt.session_flags_t.add_default_plugins)) | ||||
|         alertMask = (gt.alert.category_t.error_notification | | ||||
|                      gt.alert.category_t.storage_notification | | ||||
|                      gt.alert.category_t.tracker_notification | | ||||
|                      gt.alert.category_t.status_notification) | ||||
|         if self.config.debugAlerts: | ||||
|             alertMask |= lt.alert.category_t.debug_notification | ||||
|             alertMask |= gt.alert.category_t.debug_notification | ||||
|         self.session.set_alert_mask(alertMask) | ||||
| 
 | ||||
|         settings = self.session.get_settings() | ||||
| @ -730,12 +739,12 @@ class Pyrrent2http(object): | ||||
|         settings["tracker_backoff"] = 0 | ||||
|         ### Непонятно, как заставить использовать прокси только для подключения к трекеру? | ||||
|         if self.config.proxy is not None: | ||||
|             ps = lt.proxy_settings() | ||||
|             ps = gt.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 | ||||
|             ps.type = gt.proxy_type.socks5 | ||||
|             # self.session.set_peer_proxy(peer_ps) | ||||
|             self.session.set_proxy(ps) | ||||
|             settings['force_proxy'] = False | ||||
| @ -753,7 +762,7 @@ class Pyrrent2http(object): | ||||
|                 strerror = e.args | ||||
|                 logging.error(strerror) | ||||
|             else: | ||||
|                 self.session.load_state(lt.bdecode(bytes__)) | ||||
|                 self.session.load_state(gt.bdecode(bytes__)) | ||||
| 
 | ||||
|         rand = SystemRandom(time.time()) | ||||
|         portLower = self.config.listenPort | ||||
| @ -799,10 +808,10 @@ class Pyrrent2http(object): | ||||
|                     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 = gt.pe_settings() | ||||
|             encryptionSettings.out_enc_policy = gt.enc_policy(self.config.encryption) | ||||
|             encryptionSettings.in_enc_policy = gt.enc_policy(self.config.encryption) | ||||
|             encryptionSettings.allowed_enc_level = gt.enc_level.both | ||||
|             encryptionSettings.prefer_rc4 = True | ||||
|             self.session.set_pe_settings(encryptionSettings) | ||||
|         except Exception as e: | ||||
| @ -871,7 +880,7 @@ class Pyrrent2http(object): | ||||
|     def consumeAlerts(self): | ||||
|         alerts = self.session.pop_alerts() | ||||
|         for alert in alerts: | ||||
|             if type(alert) == lt.save_resume_data_alert: | ||||
|             if type(alert) == gt.save_resume_data_alert: | ||||
|                 self.processSaveResumeDataAlert(alert) | ||||
|                 break | ||||
| 
 | ||||
| @ -907,7 +916,7 @@ class Pyrrent2http(object): | ||||
| 
 | ||||
|     def processSaveResumeDataAlert(self, alert): | ||||
|         logging.info('Saving resume data to: %s' % (encode_msg(self.config.resumeFile),)) | ||||
|         data = lt.bencode(alert.resume_data) | ||||
|         data = gt.bencode(alert.resume_data) | ||||
|         try: | ||||
|             with open(self.config.resumeFile, 'wb') as f: | ||||
|                 f.write(data) | ||||
| @ -918,9 +927,9 @@ class Pyrrent2http(object): | ||||
|     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) | ||||
|         self.torrentHandle.save_resume_data(gt.save_resume_flags_t.flush_disk_cache) | ||||
|         if not async_: | ||||
|             alert = self.waitForAlert(lt.save_resume_data_alert, 5) | ||||
|             alert = self.waitForAlert(gt.save_resume_data_alert, 5) | ||||
|             if alert is None: | ||||
|                 return False | ||||
|             self.processSaveResumeDataAlert(alert) | ||||
| @ -930,7 +939,7 @@ class Pyrrent2http(object): | ||||
|         if self.config.stateFile == '': | ||||
|             return | ||||
|         entry = self.session.save_state() | ||||
|         data = lt.bencode(entry) | ||||
|         data = gt.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),)) | ||||
| @ -973,14 +982,14 @@ class Pyrrent2http(object): | ||||
|         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) | ||||
|                 flag = int(gt.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.waitForAlert(gt.torrent_deleted_alert, 15) | ||||
|             self.removeFiles(files) | ||||
| 
 | ||||
|     def shutdown(self): | ||||
| @ -992,7 +1001,7 @@ class Pyrrent2http(object): | ||||
|         self.TorrentFS.Shutdown() | ||||
|         if self.session != None: | ||||
|             self.session.pause() | ||||
|             self.waitForAlert(lt.torrent_paused_alert, 10) | ||||
|             self.waitForAlert(gt.torrent_paused_alert, 10) | ||||
|             if self.torrentHandle is not None: | ||||
|                 self.saveResumeData(False) | ||||
|                 self.saveSessionState() | ||||
							
								
								
									
										11
									
								
								lib/gorrent2http/log.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								lib/gorrent2http/log.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| import xbmc | ||||
| from typing import Union | ||||
| import chardet | ||||
| 
 | ||||
| 
 | ||||
| def info(msg : Union[str, bytes]): | ||||
|     xbmc.log(f'INFO: {isinstance(msg, bytes) and msg.decode(chardet.detect(msg)["encoding"]) or msg}') | ||||
| 
 | ||||
| 
 | ||||
| def error(msg: Union[str, bytes]): | ||||
|     xbmc.log(f'ERROR: {isinstance(msg, bytes) and msg.decode(chardet.detect(msg)["encoding"]) or msg}') | ||||
| @ -24,16 +24,17 @@ class Struct(dict): | ||||
|         self[attr] = value | ||||
| 
 | ||||
| 
 | ||||
| def uri2path(uri): | ||||
| def uri2path(uri: str) -> str: | ||||
|     uri_path: str = '' | ||||
|     if uri[1] == ':' and sys.platform.startswith('win'): | ||||
|         uri = 'file:///' + 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.parse.unquote(uriPath)) | ||||
|     return localize_path(absPath) | ||||
|     file_uri = urllib.parse.urlparse(uri) | ||||
|     if file_uri.scheme == 'file': | ||||
|         uri_path = file_uri.path | ||||
|         if uri_path != '' and sys.platform.startswith('win') and (os.path.sep == uri_path[0] or uri_path[0] == '/'): | ||||
|             uri_path = uri_path[1:] | ||||
|     abs_path = os.path.abspath(urllib.parse.unquote(uri_path)) | ||||
|     return localize_path(abs_path) | ||||
| 
 | ||||
| 
 | ||||
| def detect_media_type(name): | ||||
| @ -114,3 +115,62 @@ def ensure_fs_encoding(string): | ||||
|     if isinstance(string, bytes): | ||||
|         string = string.decode('utf-8') | ||||
|     return string.encode(sys.getfilesystemencoding() or 'utf-8') | ||||
| 
 | ||||
| 
 | ||||
| def get_platform(): | ||||
|         ret = { | ||||
|             "arch": sys.maxsize > 2 ** 32 and "x64" or "x86", | ||||
|         } | ||||
|         if xbmc.getCondVisibility("system.platform.android"): | ||||
|             ret["os"] = "android" | ||||
|             if "arm" in os.uname()[4] or "aarch64" in os.uname()[4]: | ||||
|                 ret["arch"] = "arm" | ||||
|         elif xbmc.getCondVisibility("system.platform.linux"): | ||||
|             ret["os"] = "linux" | ||||
|             uname = os.uname()[4] | ||||
|             if "arm" in uname: | ||||
|                 if "armv7" in uname: | ||||
|                     ret["arch"] = "armv7" | ||||
|                 else: | ||||
|                     ret["arch"] = "armv6" | ||||
|             elif "mips" in uname: | ||||
|                 ret["arch"] = 'mipsel' | ||||
|             elif "aarch64" in uname: | ||||
|                 if sys.maxsize > 2147483647:  # is_64bit_system | ||||
|                     ret["arch"] = 'aarch64' | ||||
|                 else: | ||||
|                     ret["arch"] = "armv7"  # 32-bit userspace | ||||
|         elif xbmc.getCondVisibility("system.platform.windows"): | ||||
|             ret["os"] = "windows" | ||||
|         elif xbmc.getCondVisibility("system.platform.osx"): | ||||
|             ret["os"] = "darwin" | ||||
|         elif xbmc.getCondVisibility("system.platform.ios"): | ||||
|             ret["os"] = "ios" | ||||
|             ret["arch"] = "arm" | ||||
| 
 | ||||
|         ret = get_system(ret) | ||||
|         return ret | ||||
| 
 | ||||
| 
 | ||||
| def get_system(ret): | ||||
|     ret["system"] = '' | ||||
|     if ret["os"] == 'windows': | ||||
|         ret["system"] = 'windows_' + ret['arch'] | ||||
|     elif ret["os"] == "linux" and ret["arch"] == "x64": | ||||
|         ret["system"] = 'linux_x86_64' | ||||
|     elif ret["os"] == "linux" and ret["arch"] == "x86": | ||||
|         ret["system"] = 'linux_x86' | ||||
|     elif ret["os"] == "linux" and "aarch64" in ret["arch"]: | ||||
|         ret["system"] = 'linux_' + ret["arch"] | ||||
|     elif ret["os"] == "linux" and ("arm" in ret["arch"] or 'mips' in ret["arch"]): | ||||
|         ret["system"] = 'linux_' + ret["arch"] | ||||
|     elif ret["os"] == "android": | ||||
|         if ret["arch"] == 'arm': | ||||
|             ret["system"] = 'android_armv7' | ||||
|         else: | ||||
|             ret["system"] = 'android_x86' | ||||
|     elif ret["os"] == "darwin": | ||||
|         ret["system"] = 'darwin' | ||||
|     elif ret["os"] == "ios" and ret["arch"] == "arm": | ||||
|         ret["system"] = 'ios_arm' | ||||
|     return ret | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user