рабочая версия плагина

master
Роман Бородин 2022-03-20 00:13:34 +03:00
parent 5a640c4ae4
commit ca2a815140
8 changed files with 272 additions and 239 deletions

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?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> <requires>
<import addon="xbmc.python" version="3.0.0"/> <import addon="xbmc.python" version="3.0.0"/>
<import addon="script.module.libtorrent" version="1.2.0"/>
<import addon="script.module.chardet" /> <import addon="script.module.chardet" />
</requires> </requires>
<extension point="xbmc.python.module" library="lib"/> <extension point="xbmc.python.module" library="lib"/>
@ -17,5 +16,5 @@
<description lang="ru">Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent.</description> <description lang="ru">Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent.</description>
<description lang="en">Provides sequential torrent downloading for online streaming video and other media over HTTP.</description> <description lang="en">Provides sequential torrent downloading for online streaming video and other media over HTTP.</description>
<email>inpos@yandex.ru</email> <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> </addon>

View File

@ -5,30 +5,45 @@ import urllib.error
import urllib.parse import urllib.parse
import urllib.request import urllib.request
import chardet
import sys import sys
import time import time
import xbmc import xbmc
import xbmcvfs
from . import log as logging
from . import SessionStatus, FileStatus, PeerInfo from . import FileStatus, SessionStatus
from . import pyrrent2http
from .error import Error from .error import Error
from .structs import Encryption 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 LOGGING = True
class Engine: class Engine:
""" """
This is python binding class to pyrrent2http client. This is python binding class to gorrent2http client.
""" """
def _log(self, message): def _log(self, message):
if self.logger: if self.logger:
self.logger(message) self.logger(message)
else: else:
xbmc.log("[pyrrent2http] %s" % message) xbmc.log("[gorrent2http] %s" % message)
def __init__(self, uri=None, platform=None, download_path=".", 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, 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. start() method.
:param uri: Torrent URI (magnet://, file:// or http://) :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 platform: Object with two methods implemented: arch() and system()
:param download_path: Torrent download path :param download_path: Torrent download path
:param bind_host: Bind host of torrent2http :param bind_host: Bind host of torrent2http
@ -134,16 +148,12 @@ class Engine:
self.started = False self.started = False
self.proxy = proxy self.proxy = proxy
self.message_logger = None
self.run_message_logger = True
@staticmethod @staticmethod
def _validate_save_path(path): def _validate_save_path(path):
""" path = xbmcvfs.translatePath(path)
Ensures download path can be accessed locally.
:param path: Download path
:return: Translated path
"""
import xbmc
path = xbmc.translatePath(path)
if "://" in path: if "://" in path:
if sys.platform.startswith('win') and path.lower().startswith("smb://"): if sys.platform.startswith('win') and path.lower().startswith("smb://"):
path = path.replace("smb:", "").replace("/", "\\") path = path.replace("smb:", "").replace("/", "\\")
@ -153,67 +163,16 @@ class Engine:
raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH) raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH)
return localize_path(path) return localize_path(path)
def start(self, start_index=None): def start(self, start_index):
"""
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
"""
download_path = self._validate_save_path(self.download_path) download_path = self._validate_save_path(self.download_path)
if not can_bind(self.bind_host, self.bind_port): if not can_bind(self.bind_host, self.bind_port):
port = find_free_port(self.bind_host) port = find_free_port(self.bind_host)
if port is False: 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._log("Can't bind to %s:%s, so we found another port: %d" % (self.bind_host, self.bind_port, port))
self.bind_port = port self.bind_port = port
kwargs = { self._log("Invoking gorrent2http")
'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")
class Logging(object): class Logging(object):
def __init__(self, _log): def __init__(self, _log):
@ -227,15 +186,34 @@ class Engine:
if LOGGING: if LOGGING:
self._log('ERROR: %s' % (message,)) self._log('ERROR: %s' % (message,))
pyrrent2http.logging = Logging(self._log) gt.logging = Logging(self._log)
self.pyrrent2http = pyrrent2http.Pyrrent2http(**kwargs) settings = gt.NewSettings()
self.pyrrent2http.startSession() settings.DownloadPath = download_path
self.pyrrent2http.startServices() settings.HttpBindHost = self.bind_host
self.pyrrent2http.addTorrent() settings.HttpBindPort = self.bind_port
self.pyrrent2http.startHTTP() settings.ListenPort = self.listen_port
self.pyrrent2http_loop = threading.Thread(target=self.pyrrent2http.loop) settings.TorrentPath = uri2path(self.uri)
self.pyrrent2http_loop.start() 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() start = time.time()
self.started = True self.started = True
@ -243,7 +221,7 @@ class Engine:
while (time.time() - start) < self.startup_timeout: while (time.time() - start) < self.startup_timeout:
time.sleep(0.1) time.sleep(0.1)
if not self.is_alive(): 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: try:
# self.status(1) # self.status(1)
initialized = True initialized = True
@ -253,79 +231,77 @@ class Engine:
if not initialized: if not initialized:
self.started = False self.started = False
raise Error("Can't start pyrrent2http, time is out", Error.TIMEOUT) raise Error("Can't start gorrent2http, time is out", Error.TIMEOUT)
self._log("pyrrent2http successfully started.") self._log("gorrent2http successfully started.")
def activate_file(self, index):
self.pyrrent2http.TorrentFS.file(index)
def pause(self): def pause(self):
self.pyrrent2http.pause = True pass
def resume(self): def resume(self):
self.pyrrent2http.pause = False pass
def check_torrent_error(self, status=None): 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: if not status:
status = self.status() status = self.status()
if status.error: if status.error:
raise Error("Torrent error: %s" % status.error, Error.TORRENT_ERROR, reason=status.error) raise Error("Torrent error: %s" % status.error, Error.TORRENT_ERROR, reason=status.error)
def status(self, timeout=10): def status(self, timeout=10):
""" stat = self.engine.Status()
Returns libtorrent session status. See SessionStatus named tuple. stat_kw = {
'download_rate': stat.DownloadRate,
:rtype : SessionStatus 'upload_rate': stat.UploadRate,
:param timeout: pyrrent2http client request timeout 'num_seeds': stat.Seeds,
""" 'name': '',
status = self.pyrrent2http.Status() 'state': 0,
status = SessionStatus(**status) 'state_str': '',
return status 'error': '',
'progress': 0,
def list(self, media_types=None, timeout=10): 'total_download': 0,
""" 'total_upload': 0,
Returns list of files in the torrent (see FileStatus named tuple). 'num_peers': 0,
Note that it will return None if torrent file is not loaded yet by pyrrent2http client, so you may need to call 'total_seeds': 0,
this method periodically until results are returned. 'total_peers': 0
}
:param media_types: List of media types (see MediaType constants) return SessionStatus(**stat_kw)
: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
def list_from_info(self, media_types=None): def list_from_info(self, media_types=None):
try: try:
info = pyrrent2http.lt.torrent_info(uri2path(self.uri)) info = gt.GetMetaFromFile(uri2path(self.uri))
except: except:
import traceback
xbmc.log(f'info load exception: {traceback.format_exc()}')
return [] return []
files = [] files = []
for i in range(info.num_files()): if len(info.Files) > 0:
f = info.file_at(i) for i in range(len(info.Files)):
Url = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote(f.path) f = info.Files[i]
uri = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote(
'/'.join(f.Path))
files.append({ files.append({
'name': localize_path(f.path), 'name': localize_path('/'.join(f.Path)),
'size': f.size, 'size': f.Length,
'offset': f.offset, 'offset': i,
'media_type': media_types is not None and detect_media_type(f.path) or '', 'media_type': media_types is not None and detect_media_type(f.Path[-1]) or '',
'download': 0, 'download': 0,
'progress': 0.0, 'progress': 0.0,
'save_path': '', '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: if len(files) > 0:
res = [FileStatus(index=index, **f) for index, f in enumerate(files)] res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
if media_types is not None: if media_types is not None:
@ -333,57 +309,34 @@ class Engine:
return res return res
def file_status(self, file_index, timeout=10): 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: 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: except:
raise Error("Requested file index (%d) is invalid" % (file_index,), Error.INVALID_FILE_INDEX, 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):
"""
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): def is_alive(self):
return self.pyrrent2http_loop.is_alive() return self.engine.IsAlive()
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
def close(self): 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(): if self.is_alive():
self._log("Shutting down pyrrent2http...") self._log("Shutting down gorrent2http...")
self.pyrrent2http.shutdown() self.engine.Stop()
finished = False finished = False
self.wait_on_close_timeout = 10
if self.wait_on_close_timeout is not None: if self.wait_on_close_timeout is not None:
start = time.time() start = time.time()
while (time.time() - start) < self.wait_on_close_timeout: while (time.time() - start) < self.wait_on_close_timeout:
@ -392,11 +345,12 @@ class Engine:
finished = True finished = True
break break
if not finished: if not finished:
self._log("PANIC: Timeout occurred while shutting down pyrrent2http thread") self._log("PANIC: Timeout occurred while shutting down gorrent2http thread")
else: else:
self._log("pyrrent2http successfully shut down.") self._log("gorrent2http successfully shut down.")
self.wait_on_close_timeout = None 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.started = False
self.logpipe = None
self.process = None

View File

@ -1,30 +1,38 @@
# -*- coding: utf-8 -*- # -*- 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 http.server
import io
import os
import socketserver import socketserver
import threading 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 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'): if os.getenv('ANDROID_ROOT'):
from ctypes import * from ctypes import *
@ -72,9 +80,9 @@ if not hasattr(os, 'getppid'):
def getppid(): def getppid():
''' """
:return: The pid of the parent of this process. :return: The pid of the parent of this process.
''' """
pe = PROCESSENTRY32() pe = PROCESSENTRY32()
pe.dwSize = ctypes.sizeof(PROCESSENTRY32) pe.dwSize = ctypes.sizeof(PROCESSENTRY32)
mypid = GetCurrentProcessId() mypid = GetCurrentProcessId()
@ -162,7 +170,8 @@ class TorrentFile(object):
self.tfs = tfs self.tfs = tfs
self.fileEntry = fileEntry self.fileEntry = fileEntry
self.name = self.fileEntry.path 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.media_type = detect_media_type(self.unicode_name)
self.save_path = savePath self.save_path = savePath
self.index = index self.index = index
@ -183,7 +192,7 @@ class TorrentFile(object):
return None return None
if self.filePtr is None: if self.filePtr is None:
while not os.path.exists(self.save_path): 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() self.tfs.handle.flush_cache()
time.sleep(0.5) time.sleep(0.5)
if os.getenv('ANDROID_ROOT'): if os.getenv('ANDROID_ROOT'):
@ -194,7 +203,7 @@ class TorrentFile(object):
def log(self, message): def log(self, message):
fnum = self.tfs.openedFiles.index(self) 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): def Pieces(self):
startPiece, _ = self.pieceFromOffset(1) startPiece, _ = self.pieceFromOffset(1)
@ -545,10 +554,10 @@ def HttpHandlerFactory():
return HttpHandler return HttpHandler
class Pyrrent2http(object): class Gorrent2http(object):
pause = False pause = False
def __init__(self, uri='', bindAddress='localhost:5001', downloadPath='.', def __init__(self, uri='', bind_address='localhost:5001', download_path='.',
idleTimeout=-1, keepComplete=False, idleTimeout=-1, keepComplete=False,
keepIncomplete=False, keepFiles=False, showAllStats=False, keepIncomplete=False, keepFiles=False, showAllStats=False,
showOverallProgress=False, showFilesProgress=False, showOverallProgress=False, showFilesProgress=False,
@ -568,8 +577,8 @@ class Pyrrent2http(object):
self.config = Struct() self.config = Struct()
self.config.uri = uri self.config.uri = uri
self.config.bindAddress = bindAddress self.config.bindAddress = bind_address
self.config.downloadPath = downloadPath self.config.downloadPath = download_path
self.config.idleTimeout = idleTimeout self.config.idleTimeout = idleTimeout
self.config.keepComplete = keepComplete self.config.keepComplete = keepComplete
self.config.keepIncomplete = keepIncomplete self.config.keepIncomplete = keepIncomplete
@ -618,7 +627,7 @@ class Pyrrent2http(object):
try: try:
absPath = uri2path(uri) absPath = uri2path(uri)
logging.info('Opening local torrent file: %s' % (encode_msg(absPath),)) 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: except Exception as e:
strerror = e.args strerror = e.args
logging.error('Build torrent params error is (%s)' % (strerror,)) logging.error('Build torrent params error is (%s)' % (strerror,))
@ -639,7 +648,7 @@ class Pyrrent2http(object):
logging.error(strerror) logging.error(strerror)
if self.config.noSparseFile or self.magnet: if self.config.noSparseFile or self.magnet:
logging.info('Disabling sparse file support...') 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 return torrentParams
def addTorrent(self): def addTorrent(self):
@ -704,14 +713,14 @@ class Pyrrent2http(object):
def startSession(self): def startSession(self):
logging.info('Starting session...') logging.info('Starting session...')
self.session = lt.session(lt.fingerprint('LT', lt.version_major, lt.version_minor, 0, 0), self.session = gt.session(gt.fingerprint('LT', gt.version_major, gt.version_minor, 0, 0),
flags=int(lt.session_flags_t.add_default_plugins)) flags=int(gt.session_flags_t.add_default_plugins))
alertMask = (lt.alert.category_t.error_notification | alertMask = (gt.alert.category_t.error_notification |
lt.alert.category_t.storage_notification | gt.alert.category_t.storage_notification |
lt.alert.category_t.tracker_notification | gt.alert.category_t.tracker_notification |
lt.alert.category_t.status_notification) gt.alert.category_t.status_notification)
if self.config.debugAlerts: if self.config.debugAlerts:
alertMask |= lt.alert.category_t.debug_notification alertMask |= gt.alert.category_t.debug_notification
self.session.set_alert_mask(alertMask) self.session.set_alert_mask(alertMask)
settings = self.session.get_settings() settings = self.session.get_settings()
@ -730,12 +739,12 @@ class Pyrrent2http(object):
settings["tracker_backoff"] = 0 settings["tracker_backoff"] = 0
### Непонятно, как заставить использовать прокси только для подключения к трекеру? ### Непонятно, как заставить использовать прокси только для подключения к трекеру?
if self.config.proxy is not None: if self.config.proxy is not None:
ps = lt.proxy_settings() ps = gt.proxy_settings()
# peer_ps = lt.proxy_settings() # peer_ps = lt.proxy_settings()
# peer_ps.type = lt.proxy_type.none # peer_ps.type = lt.proxy_type.none
ps.hostname = self.config.proxy['host'] ps.hostname = self.config.proxy['host']
ps.port = self.config.proxy['port'] 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_peer_proxy(peer_ps)
self.session.set_proxy(ps) self.session.set_proxy(ps)
settings['force_proxy'] = False settings['force_proxy'] = False
@ -753,7 +762,7 @@ class Pyrrent2http(object):
strerror = e.args strerror = e.args
logging.error(strerror) logging.error(strerror)
else: else:
self.session.load_state(lt.bdecode(bytes__)) self.session.load_state(gt.bdecode(bytes__))
rand = SystemRandom(time.time()) rand = SystemRandom(time.time())
portLower = self.config.listenPort portLower = self.config.listenPort
@ -799,10 +808,10 @@ class Pyrrent2http(object):
logging.info('Added DHT router: %s:%d' % (host, port)) logging.info('Added DHT router: %s:%d' % (host, port))
logging.info('Setting encryption settings') logging.info('Setting encryption settings')
try: try:
encryptionSettings = lt.pe_settings() encryptionSettings = gt.pe_settings()
encryptionSettings.out_enc_policy = lt.enc_policy(self.config.encryption) encryptionSettings.out_enc_policy = gt.enc_policy(self.config.encryption)
encryptionSettings.in_enc_policy = lt.enc_policy(self.config.encryption) encryptionSettings.in_enc_policy = gt.enc_policy(self.config.encryption)
encryptionSettings.allowed_enc_level = lt.enc_level.both encryptionSettings.allowed_enc_level = gt.enc_level.both
encryptionSettings.prefer_rc4 = True encryptionSettings.prefer_rc4 = True
self.session.set_pe_settings(encryptionSettings) self.session.set_pe_settings(encryptionSettings)
except Exception as e: except Exception as e:
@ -871,7 +880,7 @@ class Pyrrent2http(object):
def consumeAlerts(self): def consumeAlerts(self):
alerts = self.session.pop_alerts() alerts = self.session.pop_alerts()
for alert in alerts: for alert in alerts:
if type(alert) == lt.save_resume_data_alert: if type(alert) == gt.save_resume_data_alert:
self.processSaveResumeDataAlert(alert) self.processSaveResumeDataAlert(alert)
break break
@ -907,7 +916,7 @@ class Pyrrent2http(object):
def processSaveResumeDataAlert(self, alert): def processSaveResumeDataAlert(self, alert):
logging.info('Saving resume data to: %s' % (encode_msg(self.config.resumeFile),)) 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: try:
with open(self.config.resumeFile, 'wb') as f: with open(self.config.resumeFile, 'wb') as f:
f.write(data) f.write(data)
@ -918,9 +927,9 @@ class Pyrrent2http(object):
def saveResumeData(self, async_=False): def saveResumeData(self, async_=False):
if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '': if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '':
return False 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_: 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: if alert is None:
return False return False
self.processSaveResumeDataAlert(alert) self.processSaveResumeDataAlert(alert)
@ -930,7 +939,7 @@ class Pyrrent2http(object):
if self.config.stateFile == '': if self.config.stateFile == '':
return return
entry = self.session.save_state() 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),)) logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),))
try: try:
logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),)) logging.info('Saving session state to: %s' % (encode_msg(self.config.stateFile),))
@ -973,14 +982,14 @@ class Pyrrent2http(object):
state = self.torrentHandle.status().state state = self.torrentHandle.status().state
if state != state.checking_files and not self.config.keepFiles: if state != state.checking_files and not self.config.keepFiles:
if not self.config.keepComplete and not self.config.keepIncomplete: 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: else:
files = self.filesToRemove() files = self.filesToRemove()
logging.info('Removing the torrent') logging.info('Removing the torrent')
self.session.remove_torrent(self.torrentHandle, flag) self.session.remove_torrent(self.torrentHandle, flag)
if flag > 0 or len(files) > 0: if flag > 0 or len(files) > 0:
logging.info('Waiting for files to be removed') 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) self.removeFiles(files)
def shutdown(self): def shutdown(self):
@ -992,7 +1001,7 @@ class Pyrrent2http(object):
self.TorrentFS.Shutdown() self.TorrentFS.Shutdown()
if self.session != None: if self.session != None:
self.session.pause() self.session.pause()
self.waitForAlert(lt.torrent_paused_alert, 10) self.waitForAlert(gt.torrent_paused_alert, 10)
if self.torrentHandle is not None: if self.torrentHandle is not None:
self.saveResumeData(False) self.saveResumeData(False)
self.saveSessionState() self.saveSessionState()

View 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}')

View File

@ -24,16 +24,17 @@ class Struct(dict):
self[attr] = value self[attr] = value
def uri2path(uri): def uri2path(uri: str) -> str:
uri_path: str = ''
if uri[1] == ':' and sys.platform.startswith('win'): if uri[1] == ':' and sys.platform.startswith('win'):
uri = 'file:///' + uri uri = 'file:///' + uri
fileUri = urllib.parse.urlparse(uri) file_uri = urllib.parse.urlparse(uri)
if fileUri.scheme == 'file': if file_uri.scheme == 'file':
uriPath = fileUri.path uri_path = file_uri.path
if uriPath != '' and sys.platform.startswith('win') and (os.path.sep == uriPath[0] or uriPath[0] == '/'): if uri_path != '' and sys.platform.startswith('win') and (os.path.sep == uri_path[0] or uri_path[0] == '/'):
uriPath = uriPath[1:] uri_path = uri_path[1:]
absPath = os.path.abspath(urllib.parse.unquote(uriPath)) abs_path = os.path.abspath(urllib.parse.unquote(uri_path))
return localize_path(absPath) return localize_path(abs_path)
def detect_media_type(name): def detect_media_type(name):
@ -114,3 +115,62 @@ def ensure_fs_encoding(string):
if isinstance(string, bytes): if isinstance(string, bytes):
string = string.decode('utf-8') string = string.decode('utf-8')
return string.encode(sys.getfilesystemencoding() or '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