2015-01-11 08:47:17 +03:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
import os
|
2022-03-14 09:10:42 +03:00
|
|
|
import threading
|
|
|
|
import urllib.error
|
|
|
|
import urllib.parse
|
|
|
|
import urllib.request
|
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import xbmc
|
2022-03-20 00:13:34 +03:00
|
|
|
import xbmcvfs
|
|
|
|
from . import log as logging
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
from . import FileStatus, SessionStatus
|
2022-03-14 09:10:42 +03:00
|
|
|
from .error import Error
|
|
|
|
from .structs import Encryption
|
2022-03-20 00:13:34 +03:00
|
|
|
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
|
2015-01-11 08:47:17 +03:00
|
|
|
|
2016-03-06 13:19:14 +03:00
|
|
|
LOGGING = True
|
2015-01-11 08:47:17 +03:00
|
|
|
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
class Engine:
|
2015-01-29 13:34:13 +03:00
|
|
|
"""
|
2022-03-20 00:13:34 +03:00
|
|
|
This is python binding class to gorrent2http client.
|
2015-01-29 13:34:13 +03:00
|
|
|
"""
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
def _log(self, message):
|
|
|
|
if self.logger:
|
|
|
|
self.logger(message)
|
|
|
|
else:
|
2022-03-20 00:13:34 +03:00
|
|
|
xbmc.log("[gorrent2http] %s" % message)
|
2015-01-11 08:47:17 +03:00
|
|
|
|
2022-04-30 20:22:52 +03:00
|
|
|
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,
|
|
|
|
):
|
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
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
|
2015-01-16 13:32:33 +03:00
|
|
|
self.started = False
|
2020-04-28 22:25:14 +03:00
|
|
|
self.proxy = proxy
|
2022-04-30 20:22:52 +03:00
|
|
|
self.seed = seed
|
|
|
|
self.accept_peer_conn = accept_peer_conn
|
|
|
|
self.startup_timeout = startup_timeout
|
|
|
|
self.logger = logger
|
2015-01-11 08:47:17 +03:00
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
self.message_logger = None
|
|
|
|
self.run_message_logger = True
|
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
@staticmethod
|
|
|
|
def _validate_save_path(path):
|
2022-03-20 00:13:34 +03:00
|
|
|
path = xbmcvfs.translatePath(path)
|
2015-01-11 08:47:17 +03:00
|
|
|
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)
|
2016-03-11 22:32:02 +03:00
|
|
|
if not os.path.isdir(localize_path(path)):
|
2015-01-11 08:47:17 +03:00
|
|
|
raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH)
|
2016-03-12 19:45:18 +03:00
|
|
|
return localize_path(path)
|
2015-01-11 08:47:17 +03:00
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
def start(self, start_index):
|
2015-01-11 08:47:17 +03:00
|
|
|
download_path = self._validate_save_path(self.download_path)
|
2015-01-29 13:34:13 +03:00
|
|
|
if not can_bind(self.bind_host, self.bind_port):
|
|
|
|
port = find_free_port(self.bind_host)
|
2015-01-11 08:47:17 +03:00
|
|
|
if port is False:
|
2022-03-20 00:13:34 +03:00
|
|
|
raise Error("Can't find port to bind gorrent2http", Error.BIND_ERROR)
|
2015-01-11 08:47:17 +03:00
|
|
|
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
|
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
self._log("Invoking gorrent2http")
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2016-03-04 17:41:28 +03:00
|
|
|
class Logging(object):
|
|
|
|
def __init__(self, _log):
|
|
|
|
self._log = _log
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2016-03-04 17:41:28 +03:00
|
|
|
def info(self, message):
|
2016-03-04 19:27:34 +03:00
|
|
|
if LOGGING:
|
|
|
|
self._log('INFO: %s' % (message,))
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2016-03-04 17:41:28 +03:00
|
|
|
def error(self, message):
|
2016-03-04 19:27:34 +03:00
|
|
|
if LOGGING:
|
|
|
|
self._log('ERROR: %s' % (message,))
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
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 ''
|
2022-04-30 20:22:52 +03:00
|
|
|
settings.Seed = self.seed
|
|
|
|
settings.AcceptPeerConnections = self.accept_peer_conn
|
2022-03-20 00:13:34 +03:00
|
|
|
|
|
|
|
self.engine = gt.NewEngine(settings)
|
2022-04-30 20:22:52 +03:00
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
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')
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2022-03-20 00:13:34 +03:00
|
|
|
self.engine.StartTorrent(start_index)
|
|
|
|
|
|
|
|
self._log('waiting alive status set')
|
2015-01-11 08:47:17 +03:00
|
|
|
|
2016-03-12 19:45:18 +03:00
|
|
|
start = time.time()
|
2015-01-16 13:32:33 +03:00
|
|
|
self.started = True
|
2015-01-11 08:47:17 +03:00
|
|
|
initialized = False
|
|
|
|
while (time.time() - start) < self.startup_timeout:
|
|
|
|
time.sleep(0.1)
|
|
|
|
if not self.is_alive():
|
2022-03-20 00:13:34 +03:00
|
|
|
raise Error("Can't start gorrent2http, see log for details", Error.PROCESS_ERROR)
|
2015-01-11 08:47:17 +03:00
|
|
|
try:
|
2022-03-14 09:10:42 +03:00
|
|
|
# self.status(1)
|
2015-01-11 08:47:17 +03:00
|
|
|
initialized = True
|
|
|
|
break
|
|
|
|
except Error:
|
|
|
|
pass
|
|
|
|
|
|
|
|
if not initialized:
|
2015-01-16 13:32:33 +03:00
|
|
|
self.started = False
|
2022-03-20 00:13:34 +03:00
|
|
|
raise Error("Can't start gorrent2http, time is out", Error.TIMEOUT)
|
|
|
|
self._log("gorrent2http successfully started.")
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2016-03-27 15:27:14 +03:00
|
|
|
def pause(self):
|
2022-03-20 00:13:34 +03:00
|
|
|
pass
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2016-03-27 15:27:14 +03:00
|
|
|
def resume(self):
|
2022-03-20 00:13:34 +03:00
|
|
|
pass
|
2016-03-19 22:00:16 +03:00
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
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):
|
2022-03-20 00:13:34 +03:00
|
|
|
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)
|
2022-03-14 09:10:42 +03:00
|
|
|
|
2016-03-19 17:25:09 +03:00
|
|
|
def list_from_info(self, media_types=None):
|
2016-03-12 02:29:23 +03:00
|
|
|
try:
|
2022-03-20 00:13:34 +03:00
|
|
|
info = gt.GetMetaFromFile(uri2path(self.uri))
|
2016-03-12 02:29:23 +03:00
|
|
|
except:
|
2022-03-20 00:13:34 +03:00
|
|
|
import traceback
|
|
|
|
xbmc.log(f'info load exception: {traceback.format_exc()}')
|
2016-03-12 02:29:23 +03:00
|
|
|
return []
|
|
|
|
files = []
|
2022-03-20 00:13:34 +03:00
|
|
|
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:
|
2016-03-12 02:29:23 +03:00
|
|
|
files.append({
|
2022-03-20 00:13:34 +03:00
|
|
|
'name': localize_path(info.Name),
|
|
|
|
'size': info.Length,
|
|
|
|
'offset': 0,
|
|
|
|
'media_type': media_types is not None and detect_media_type(info.Name) or '',
|
2022-03-14 09:10:42 +03:00
|
|
|
'download': 0,
|
|
|
|
'progress': 0.0,
|
|
|
|
'save_path': '',
|
2022-03-20 00:13:34 +03:00
|
|
|
'url': 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote(
|
|
|
|
info.Name)
|
2022-03-14 09:10:42 +03:00
|
|
|
})
|
2022-03-20 00:13:34 +03:00
|
|
|
res = []
|
2022-03-14 09:10:42 +03:00
|
|
|
if len(files) > 0:
|
2016-03-12 02:29:23 +03:00
|
|
|
res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
|
2016-03-19 17:25:09 +03:00
|
|
|
if media_types is not None:
|
2022-03-14 09:10:42 +03:00
|
|
|
res = [fs for fs in res if fs.media_type in media_types]
|
2016-03-12 02:29:23 +03:00
|
|
|
return res
|
2015-01-11 08:47:17 +03:00
|
|
|
|
|
|
|
def file_status(self, file_index, timeout=10):
|
2016-03-19 22:00:16 +03:00
|
|
|
try:
|
2022-03-20 00:13:34 +03:00
|
|
|
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)
|
2016-03-19 22:00:16 +03:00
|
|
|
except:
|
2016-03-19 17:25:09 +03:00
|
|
|
raise Error("Requested file index (%d) is invalid" % (file_index,), Error.INVALID_FILE_INDEX,
|
2022-03-14 09:10:42 +03:00
|
|
|
file_index=file_index)
|
2015-01-11 08:47:17 +03:00
|
|
|
|
|
|
|
def is_alive(self):
|
2022-03-20 00:13:34 +03:00
|
|
|
return self.engine.IsAlive()
|
2015-01-11 08:47:17 +03:00
|
|
|
|
|
|
|
def close(self):
|
2016-03-12 19:45:18 +03:00
|
|
|
if self.is_alive():
|
2022-03-20 00:13:34 +03:00
|
|
|
self._log("Shutting down gorrent2http...")
|
|
|
|
self.engine.Stop()
|
2016-03-11 22:32:02 +03:00
|
|
|
finished = False
|
2022-03-20 00:13:34 +03:00
|
|
|
|
|
|
|
self.wait_on_close_timeout = 10
|
|
|
|
|
2015-01-11 08:47:17 +03:00
|
|
|
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:
|
2022-03-20 00:13:34 +03:00
|
|
|
self._log("PANIC: Timeout occurred while shutting down gorrent2http thread")
|
2015-01-11 08:47:17 +03:00
|
|
|
else:
|
2022-03-20 00:13:34 +03:00
|
|
|
self._log("gorrent2http successfully shut down.")
|
2016-03-11 22:32:02 +03:00
|
|
|
self.wait_on_close_timeout = None
|
2022-03-20 00:13:34 +03:00
|
|
|
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
|
2015-01-16 13:32:33 +03:00
|
|
|
self.started = False
|