рабочая версия плагина
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('/'.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
|
|
@ -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()
|
|
@ -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…
Reference in New Issue