From ca2a815140725502d5ea083fd1038860bb50db06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D1=80=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=A0=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD?= Date: Sun, 20 Mar 2022 00:13:34 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BF=D0=BB=D0=B0=D0=B3?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addon.xml | 5 +- .../__init__.py | 0 lib/{pyrrent2http => gorrent2http}/engine.py | 302 ++++++++---------- lib/{pyrrent2http => gorrent2http}/error.py | 0 .../gorrent2http.py} | 117 +++---- lib/gorrent2http/log.py | 11 + lib/{pyrrent2http => gorrent2http}/structs.py | 0 lib/{pyrrent2http => gorrent2http}/util.py | 76 ++++- 8 files changed, 272 insertions(+), 239 deletions(-) rename lib/{pyrrent2http => gorrent2http}/__init__.py (100%) rename lib/{pyrrent2http => gorrent2http}/engine.py (56%) rename lib/{pyrrent2http => gorrent2http}/error.py (100%) rename lib/{pyrrent2http/pyrrent2http.py => gorrent2http/gorrent2http.py} (93%) create mode 100644 lib/gorrent2http/log.py rename lib/{pyrrent2http => gorrent2http}/structs.py (100%) rename lib/{pyrrent2http => gorrent2http}/util.py (51%) diff --git a/addon.xml b/addon.xml index e51112f..e0ade66 100644 --- a/addon.xml +++ b/addon.xml @@ -1,8 +1,7 @@ - + - @@ -17,5 +16,5 @@ Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent. Provides sequential torrent downloading for online streaming video and other media over HTTP. inpos@yandex.ru - https://git.ukamnya.ru/ukamnya/script.module.pyrrent2http + https://git.ukamnya.ru/ukamnya/script.module.gorrent2http diff --git a/lib/pyrrent2http/__init__.py b/lib/gorrent2http/__init__.py similarity index 100% rename from lib/pyrrent2http/__init__.py rename to lib/gorrent2http/__init__.py diff --git a/lib/pyrrent2http/engine.py b/lib/gorrent2http/engine.py similarity index 56% rename from lib/pyrrent2http/engine.py rename to lib/gorrent2http/engine.py index c1871ec..e8a8a5e 100644 --- a/lib/pyrrent2http/engine.py +++ b/lib/gorrent2http/engine.py @@ -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('/'.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': uri + }) + else: 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(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': Url + '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 diff --git a/lib/pyrrent2http/error.py b/lib/gorrent2http/error.py similarity index 100% rename from lib/pyrrent2http/error.py rename to lib/gorrent2http/error.py diff --git a/lib/pyrrent2http/pyrrent2http.py b/lib/gorrent2http/gorrent2http.py similarity index 93% rename from lib/pyrrent2http/pyrrent2http.py rename to lib/gorrent2http/gorrent2http.py index eafd24a..c7cdb6a 100755 --- a/lib/pyrrent2http/pyrrent2http.py +++ b/lib/gorrent2http/gorrent2http.py @@ -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() diff --git a/lib/gorrent2http/log.py b/lib/gorrent2http/log.py new file mode 100644 index 0000000..bae25a1 --- /dev/null +++ b/lib/gorrent2http/log.py @@ -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}') diff --git a/lib/pyrrent2http/structs.py b/lib/gorrent2http/structs.py similarity index 100% rename from lib/pyrrent2http/structs.py rename to lib/gorrent2http/structs.py diff --git a/lib/pyrrent2http/util.py b/lib/gorrent2http/util.py similarity index 51% rename from lib/pyrrent2http/util.py rename to lib/gorrent2http/util.py index 18fbca0..b75b8c6 100644 --- a/lib/pyrrent2http/util.py +++ b/lib/gorrent2http/util.py @@ -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