script.module.gorrent2http/lib/gorrent2http/engine.py

286 lines
9.6 KiB
Python

# -*- coding: utf-8 -*-
import os
import threading
import urllib.error
import urllib.parse
import urllib.request
import sys
import time
import xbmc
import xbmcvfs
from . import log as logging
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, 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 gorrent2http client.
"""
def _log(self, message):
if self.logger:
self.logger(message)
else:
xbmc.log("[gorrent2http] %s" % message)
def __init__(self, uri=None, download_path=".",
bind_host='127.0.0.1', bind_port=5001, connections_limit=200,
keep_files=False,
listen_port=6881,
debug_alerts=False,
proxy=None,
seed=True,
accept_peer_conn=True,
startup_timeout=5,
logger=None,
):
self.platform = platform
self.bind_host = bind_host
self.bind_port = bind_port
self.download_path = download_path
self.connections_limit = connections_limit
self.keep_files = keep_files
self.listen_port = listen_port
self.wait_on_close_timeout = None
self.debug_alerts = debug_alerts
self.uri = uri
self.started = False
self.proxy = proxy
self.seed = seed
self.accept_peer_conn = accept_peer_conn
self.startup_timeout = startup_timeout
self.logger = logger
self.message_logger = None
self.run_message_logger = True
@staticmethod
def _validate_save_path(path):
path = xbmcvfs.translatePath(path)
if "://" in path:
if sys.platform.startswith('win') and path.lower().startswith("smb://"):
path = path.replace("smb:", "").replace("/", "\\")
else:
raise Error("Downloading to an unmounted network share is not supported", Error.INVALID_DOWNLOAD_PATH)
if not os.path.isdir(localize_path(path)):
raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH)
return localize_path(path)
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 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
self._log("Invoking gorrent2http")
class Logging(object):
def __init__(self, _log):
self._log = _log
def info(self, message):
if LOGGING:
self._log('INFO: %s' % (message,))
def error(self, message):
if LOGGING:
self._log('ERROR: %s' % (message,))
gt.logging = Logging(self._log)
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 ''
settings.Seed = self.seed
settings.AcceptPeerConnections = self.accept_peer_conn
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
initialized = False
while (time.time() - start) < self.startup_timeout:
time.sleep(0.1)
if not self.is_alive():
raise Error("Can't start gorrent2http, see log for details", Error.PROCESS_ERROR)
try:
# self.status(1)
initialized = True
break
except Error:
pass
if not initialized:
self.started = False
raise Error("Can't start gorrent2http, time is out", Error.TIMEOUT)
self._log("gorrent2http successfully started.")
def pause(self):
pass
def resume(self):
pass
def check_torrent_error(self, status=None):
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):
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 = gt.GetMetaFromFile(uri2path(self.uri))
except:
import traceback
xbmc.log(f'info load exception: {traceback.format_exc()}')
return []
files = []
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(info.Name),
'size': info.Length,
'offset': 0,
'media_type': media_types is not None and detect_media_type(info.Name) or '',
'download': 0,
'progress': 0.0,
'save_path': '',
'url': 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote(
info.Name)
})
res = []
if len(files) > 0:
res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
if media_types is not None:
res = [fs for fs in res if fs.media_type in media_types]
return res
def file_status(self, file_index, timeout=10):
try:
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 is_alive(self):
return self.engine.IsAlive()
def close(self):
if self.is_alive():
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:
time.sleep(0.5)
if not self.is_alive():
finished = True
break
if not finished:
self._log("PANIC: Timeout occurred while shutting down gorrent2http thread")
else:
self._log("gorrent2http successfully shut down.")
self.wait_on_close_timeout = None
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