Added code comments, better support of unicode path names
parent
10d91a1d2d
commit
7fcf3e20ab
|
@ -0,0 +1,60 @@
|
||||||
|
script.module.torrent2http
|
||||||
|
==========================
|
||||||
|
|
||||||
|
This add-on is binding to [torrent2http](https://github.com/anteo/torrent2http) client.
|
||||||
|
It is bundled with torrent2http binaries for Android ARM, Linux x86/x64/ARM, Windows x86/x64 and Darwin/OSX x64 platforms.
|
||||||
|
You can download it from my [repository](http://bit.ly/184XKjm)
|
||||||
|
|
||||||
|
This add-on can be used to stream media files from torrents without need to download entire files.
|
||||||
|
|
||||||
|
Internally, it runs pre-compiled torrent2http client binary, which starts local HTTP server, presenting contents of torrent.
|
||||||
|
Next, request to HTTP server can be sent to receive list of files or to start streaming of needed file inside of torrent.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
### Get list of files inside torrent ###
|
||||||
|
|
||||||
|
Getting list of files inside torrent is straightforward:
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
from torrent2http import State, Engine, MediaType
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
# Create instance of Engine
|
||||||
|
engine = Engine(uri="...")
|
||||||
|
files = []
|
||||||
|
# Ensure we'll close engine on exception
|
||||||
|
with closing(engine):
|
||||||
|
# Start engine
|
||||||
|
engine.start()
|
||||||
|
# Wait until files received
|
||||||
|
while not files and not xbmc.abortRequested:
|
||||||
|
# Will list only video files in torrent
|
||||||
|
files = engine.list(media_types=[MediaType.VIDEO])
|
||||||
|
# Check if there is loading torrent error and raise exception
|
||||||
|
engine.check_torrent_error()
|
||||||
|
xbmc.sleep(200)
|
||||||
|
|
||||||
|
### Start streaming ###
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
from torrent2http import State, Engine, MediaType
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
# We can know file_id of needed video file on this step, if no, we'll try to detect one.
|
||||||
|
file_id = None
|
||||||
|
engine = Engine(uri="...")
|
||||||
|
with closing(self.engine):
|
||||||
|
# Start engine and instruct torrent2http to begin download first file,
|
||||||
|
# so it can start searching and connecting to peers
|
||||||
|
self.engine.start(file_id or 0)
|
||||||
|
while not xbmc.abortRequested:
|
||||||
|
sleep(self.SLEEP_DELAY)
|
||||||
|
status = self.engine.status()
|
||||||
|
self.engine.check_torrent_error(status)
|
||||||
|
if status.state in [State.DOWNLOADING, State.FINISHED, State.SEEDING]:
|
||||||
|
ready = True
|
||||||
|
break
|
||||||
|
|
||||||
|
... todo ...
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="script.module.torrent2http" name="torrent2http" version="0.0.5" provider-name="anteo">
|
<addon id="script.module.torrent2http" name="torrent2http" version="0.0.6" provider-name="anteo">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.14.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
</requires>
|
</requires>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
[B]Version 0.0.6[/B]
|
||||||
|
+ Added code comments
|
||||||
|
* Better support of unicode path names
|
||||||
|
|
||||||
[B]Version 0.0.5[/B]
|
[B]Version 0.0.5[/B]
|
||||||
+ Torrent2http binaries updated to 1.0.1
|
+ Torrent2http binaries updated to 1.0.1
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,22 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib2
|
import urllib2
|
||||||
|
import httplib
|
||||||
|
from os.path import dirname
|
||||||
|
|
||||||
import logpipe
|
import logpipe
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
from error import Error
|
from error import Error
|
||||||
from platform import Platform
|
from platform import Platform
|
||||||
from . import SessionStatus, FileStatus, PeerInfo, MediaType, Encryption
|
from . import SessionStatus, FileStatus, PeerInfo, MediaType, Encryption
|
||||||
from os.path import dirname
|
from util import can_bind, find_free_port, ensure_fs_encoding
|
||||||
|
|
||||||
|
|
||||||
class Engine:
|
class Engine:
|
||||||
|
"""
|
||||||
|
This is python binding class to torrent2http client.
|
||||||
|
"""
|
||||||
SUBTITLES_FORMATS = ['.aqt', '.gsub', '.jss', '.sub', '.ttxt', '.pjs', '.psb', '.rt', '.smi', '.stl',
|
SUBTITLES_FORMATS = ['.aqt', '.gsub', '.jss', '.sub', '.ttxt', '.pjs', '.psb', '.rt', '.smi', '.stl',
|
||||||
'.ssf', '.srt', '.ssa', '.ass', '.usf', '.idx']
|
'.ssf', '.srt', '.ssa', '.ass', '.usf', '.idx']
|
||||||
|
|
||||||
|
@ -42,6 +47,12 @@ class Engine:
|
||||||
xbmc.log("[torrent2http] %s" % message)
|
xbmc.log("[torrent2http] %s" % message)
|
||||||
|
|
||||||
def _get_binary_path(self, binaries_path):
|
def _get_binary_path(self, binaries_path):
|
||||||
|
"""
|
||||||
|
Detects platform and returns corresponding torrent2http binary path
|
||||||
|
|
||||||
|
:param binaries_path:
|
||||||
|
:return: torrent2http binary path
|
||||||
|
"""
|
||||||
binary = "torrent2http" + (".exe" if self.platform.system == 'windows' else "")
|
binary = "torrent2http" + (".exe" if self.platform.system == 'windows' else "")
|
||||||
binary_dir = os.path.join(binaries_path, "%s_%s" % (self.platform.system, self.platform.arch))
|
binary_dir = os.path.join(binaries_path, "%s_%s" % (self.platform.system, self.platform.arch))
|
||||||
binary_path = os.path.join(binary_dir, binary)
|
binary_path = os.path.join(binary_dir, binary)
|
||||||
|
@ -74,27 +85,6 @@ class Engine:
|
||||||
self._log("Selected %s as torrent2http binary" % binary_path)
|
self._log("Selected %s as torrent2http binary" % binary_path)
|
||||||
return binary_path
|
return binary_path
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _can_bind(host, port):
|
|
||||||
try:
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
s.bind((host, port))
|
|
||||||
s.close()
|
|
||||||
except socket.error:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _find_free_port(host):
|
|
||||||
try:
|
|
||||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
s.bind((host, 0))
|
|
||||||
port = s.getsockname()[1]
|
|
||||||
s.close()
|
|
||||||
except socket.error:
|
|
||||||
return False
|
|
||||||
return port
|
|
||||||
|
|
||||||
def __init__(self, uri=None, binaries_path=None, platform=None, download_path=".",
|
def __init__(self, uri=None, binaries_path=None, platform=None, download_path=".",
|
||||||
bind_host='127.0.0.1', bind_port=5001, connections_limit=None, download_kbps=None, upload_kbps=None,
|
bind_host='127.0.0.1', bind_port=5001, connections_limit=None, download_kbps=None, upload_kbps=None,
|
||||||
enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False,
|
enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False,
|
||||||
|
@ -105,6 +95,55 @@ class Engine:
|
||||||
debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50,
|
debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50,
|
||||||
peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3,
|
peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3,
|
||||||
dht_routers=None, trackers=None):
|
dht_routers=None, trackers=None):
|
||||||
|
"""
|
||||||
|
Creates engine instance. It doesn't do anything except initializing object members. For starting engine use
|
||||||
|
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
|
||||||
|
:param bind_port: Bind port of torrent2http
|
||||||
|
:param connections_limit: Set a global limit on the number of connections opened
|
||||||
|
:param download_kbps: Max download rate (kB/s)
|
||||||
|
:param upload_kbps: Max upload rate (kB/s)
|
||||||
|
:param enable_dht: Enable DHT (Distributed Hash Table)
|
||||||
|
:param enable_lsd: Enable LSD (Local Service Discovery)
|
||||||
|
:param enable_natpmp: Enable NATPMP (NAT port-mapping)
|
||||||
|
:param enable_upnp: Enable UPnP (UPnP port-mapping)
|
||||||
|
:param enable_scrape: Enable sending scrape request to tracker (updates total peers/seeds count)
|
||||||
|
:param log_stats: Log all stats (incl. log_overall_progress, log_files_progress, log_pieces_progress)
|
||||||
|
:param encryption: Encryption: 0=forced 1=enabled (default) 2=disabled
|
||||||
|
:param keep_complete: Keep complete files after exiting
|
||||||
|
:param keep_incomplete: Keep incomplete files after exiting
|
||||||
|
:param keep_files: Keep all files after exiting (incl. keep_complete and keep_incomplete)
|
||||||
|
:param log_files_progress: Log files progress
|
||||||
|
:param log_overall_progress: Log overall progress
|
||||||
|
:param log_pieces_progress: Log pieces progress
|
||||||
|
:param listen_port: Use specified port for incoming connections
|
||||||
|
:param use_random_port: Use random listen port (49152-65535)
|
||||||
|
:param max_idle_timeout: Automatically shutdown torrent2http if no connection are active after a timeout
|
||||||
|
:param no_sparse: Do not use sparse file allocation
|
||||||
|
:param resume_file: Use fast resume file
|
||||||
|
:param user_agent: Set an user agent
|
||||||
|
:param startup_timeout: torrent2http startup timeout
|
||||||
|
:param state_file: Use file for saving/restoring session state
|
||||||
|
:param enable_utp: Enable uTP protocol
|
||||||
|
:param enable_tcp: Enable TCP protocol
|
||||||
|
:param debug_alerts: Show debug alert notifications
|
||||||
|
:param logger: Instance of logging.Logger
|
||||||
|
:param torrent_connect_boost: The number of peers to try to connect to immediately when the first tracker
|
||||||
|
response is received for a torrent
|
||||||
|
:param connection_speed: The number of peer connection attempts that are made per second
|
||||||
|
:param peer_connect_timeout: The number of seconds to wait after a connection attempt is initiated to a peer
|
||||||
|
:param request_timeout: The number of seconds until the current front piece request will time out
|
||||||
|
:param min_reconnect_time: The time to wait between peer connection attempts. If the peer fails, the time is
|
||||||
|
multiplied by fail counter
|
||||||
|
:param max_failcount: The maximum times we try to connect to a peer before stop connecting again
|
||||||
|
:param dht_routers: List of additional DHT routers (host:port pairs)
|
||||||
|
:param trackers: List of additional tracker URLs
|
||||||
|
"""
|
||||||
self.dht_routers = dht_routers or []
|
self.dht_routers = dht_routers or []
|
||||||
self.trackers = trackers or []
|
self.trackers = trackers or []
|
||||||
self.max_failcount = max_failcount
|
self.max_failcount = max_failcount
|
||||||
|
@ -154,21 +193,36 @@ class Engine:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_save_path(path):
|
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)
|
||||||
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("/", "\\")
|
||||||
else:
|
else:
|
||||||
raise Error("Downloading to an unmounted network share is not supported", Error.INVALID_DOWNLOAD_PATH)
|
raise Error("Downloading to an unmounted network share is not supported", Error.INVALID_DOWNLOAD_PATH)
|
||||||
if not os.path.isdir(path):
|
if not os.path.isdir(ensure_fs_encoding(path)):
|
||||||
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 path
|
return path
|
||||||
|
|
||||||
def start(self, start_index=None):
|
def start(self, start_index=None):
|
||||||
|
"""
|
||||||
|
Starts torrent2http 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
|
||||||
|
"""
|
||||||
self.platform = self.platform or Platform()
|
self.platform = self.platform or Platform()
|
||||||
binary_path = self._get_binary_path(self.binaries_path)
|
binary_path = self._get_binary_path(self.binaries_path)
|
||||||
download_path = self._validate_save_path(self.download_path)
|
download_path = self._validate_save_path(self.download_path)
|
||||||
if not self._can_bind(self.bind_host, self.bind_port):
|
if not can_bind(self.bind_host, self.bind_port):
|
||||||
port = self._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 torrent2http", Error.BIND_ERROR)
|
raise Error("Can't find port to bind torrent2http", 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))
|
||||||
|
@ -225,10 +279,8 @@ class Engine:
|
||||||
args.append("%s=false" % k)
|
args.append("%s=false" % k)
|
||||||
else:
|
else:
|
||||||
args.append(k)
|
args.append(k)
|
||||||
if isinstance(v, str):
|
if isinstance(v, str) or isinstance(v, unicode):
|
||||||
v = v.decode('utf-8')
|
v = ensure_fs_encoding(v)
|
||||||
if isinstance(v, unicode):
|
|
||||||
v = v.encode(sys.getfilesystemencoding() or 'utf-8')
|
|
||||||
else:
|
else:
|
||||||
v = str(v)
|
v = str(v)
|
||||||
args.append(v)
|
args.append(v)
|
||||||
|
@ -266,12 +318,25 @@ class Engine:
|
||||||
self._log("torrent2http successfully started.")
|
self._log("torrent2http successfully started.")
|
||||||
|
|
||||||
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 torrent2http 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):
|
||||||
|
"""
|
||||||
|
Returns libtorrent session status. See SessionStatus named tuple.
|
||||||
|
|
||||||
|
:rtype : SessionStatus
|
||||||
|
:param timeout: torrent2http client request timeout
|
||||||
|
"""
|
||||||
status = self._decode(self._request('status', timeout))
|
status = self._decode(self._request('status', timeout))
|
||||||
status = SessionStatus(**status)
|
status = SessionStatus(**status)
|
||||||
return status
|
return status
|
||||||
|
@ -293,6 +358,16 @@ class Engine:
|
||||||
return MediaType.UNKNOWN
|
return MediaType.UNKNOWN
|
||||||
|
|
||||||
def list(self, media_types=None, timeout=10):
|
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 torrent2http 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: torrent2http 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._decode(self._request('ls', timeout))['files']
|
files = self._decode(self._request('ls', timeout))['files']
|
||||||
if files:
|
if files:
|
||||||
res = [FileStatus(index=index, media_type=self._detect_media_type(f['name']), **f)
|
res = [FileStatus(index=index, media_type=self._detect_media_type(f['name']), **f)
|
||||||
|
@ -302,6 +377,16 @@ 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 torrent2http client, so you may need to call
|
||||||
|
this method periodically until results are returned.
|
||||||
|
|
||||||
|
:param file_index: Requested file's index
|
||||||
|
:param timeout: torrent2http client request timeout
|
||||||
|
:return: File with specified index
|
||||||
|
:rtype: FileStatus
|
||||||
|
"""
|
||||||
res = self.list(timeout=timeout)
|
res = self.list(timeout=timeout)
|
||||||
if res:
|
if res:
|
||||||
try:
|
try:
|
||||||
|
@ -311,6 +396,13 @@ class Engine:
|
||||||
file_index=file_index)
|
file_index=file_index)
|
||||||
|
|
||||||
def peers(self, timeout=10):
|
def peers(self, timeout=10):
|
||||||
|
"""
|
||||||
|
Returns list of peers connected (see PeerInfo named tuple).
|
||||||
|
|
||||||
|
:param timeout: torrent2http client request timeout
|
||||||
|
:return: List of peers
|
||||||
|
:rtype: list of PeerInfo
|
||||||
|
"""
|
||||||
peers = self._decode(self._request('peers', timeout))['peers']
|
peers = self._decode(self._request('peers', timeout))['peers']
|
||||||
if peers:
|
if peers:
|
||||||
return [PeerInfo(**p) for p in peers]
|
return [PeerInfo(**p) for p in peers]
|
||||||
|
@ -334,8 +426,8 @@ class Engine:
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
kwargs['timeout'] = timeout
|
kwargs['timeout'] = timeout
|
||||||
return urllib2.urlopen(url, **kwargs).read()
|
return urllib2.urlopen(url, **kwargs).read()
|
||||||
except urllib2.URLError as e:
|
except (urllib2.URLError, httplib.HTTPException) as e:
|
||||||
if isinstance(e.reason, socket.timeout):
|
if isinstance(e, urllib2.URLError) and isinstance(e.reason, socket.timeout):
|
||||||
raise Error("Timeout occurred while sending command '%s' to torrent2http" % cmd, Error.TIMEOUT)
|
raise Error("Timeout occurred while sending command '%s' to torrent2http" % cmd, Error.TIMEOUT)
|
||||||
elif not self.is_alive() and self.started:
|
elif not self.is_alive() and self.started:
|
||||||
raise Error("torrent2http has crashed.", Error.CRASHED)
|
raise Error("torrent2http has crashed.", Error.CRASHED)
|
||||||
|
@ -346,9 +438,20 @@ class Engine:
|
||||||
raise Error("Can't read from torrent2http: %s" % reason, Error.REQUEST_ERROR)
|
raise Error("Can't read from torrent2http: %s" % reason, Error.REQUEST_ERROR)
|
||||||
|
|
||||||
def wait_on_close(self, wait_timeout=10):
|
def wait_on_close(self, wait_timeout=10):
|
||||||
|
"""
|
||||||
|
By default, close() method sends shutdown command to torrent2http, stops logging and returns immediately, not
|
||||||
|
waiting while torrent2http exits. It can be handy to wait torrent2http 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 torrent2http client shut down
|
||||||
|
"""
|
||||||
self.wait_on_close_timeout = wait_timeout
|
self.wait_on_close_timeout = wait_timeout
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
"""
|
||||||
|
Shuts down torrent2http and stops logging. If wait_on_close() was called earlier, it will wait until
|
||||||
|
torrent2http successfully exits.
|
||||||
|
"""
|
||||||
if self.logpipe and self.wait_on_close_timeout is None:
|
if self.logpipe and self.wait_on_close_timeout is None:
|
||||||
self.logpipe.close()
|
self.logpipe.close()
|
||||||
if self.is_alive():
|
if self.is_alive():
|
||||||
|
@ -371,4 +474,4 @@ class Engine:
|
||||||
self.wait_on_close_timeout = None
|
self.wait_on_close_timeout = None
|
||||||
self.started = False
|
self.started = False
|
||||||
self.logpipe = None
|
self.logpipe = None
|
||||||
self.process = None
|
self.process = None
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
|
|
||||||
class Error(Exception):
|
class Error(Exception):
|
||||||
TORRENT_ERROR = 12
|
TORRENT_ERROR = 12
|
||||||
|
""" Error returned by libtorrent """
|
||||||
CRASHED = 13
|
CRASHED = 13
|
||||||
|
""" torrent2http client has crashed abnormally """
|
||||||
UNKNOWN_PLATFORM = 1
|
UNKNOWN_PLATFORM = 1
|
||||||
|
""" Unknown/unsupported platform """
|
||||||
XBMC_HOME_NOT_DEFINED = 2
|
XBMC_HOME_NOT_DEFINED = 2
|
||||||
|
""" XBMC_HOME or KODI_HOME is not set """
|
||||||
NOEXEC_FILESYSTEM = 3
|
NOEXEC_FILESYSTEM = 3
|
||||||
|
""" torrent2http binary is placed on noexec filesystem, so it can't be started """
|
||||||
REQUEST_ERROR = 5
|
REQUEST_ERROR = 5
|
||||||
|
""" Error occurred while sending request to torrent2http """
|
||||||
INVALID_DOWNLOAD_PATH = 6
|
INVALID_DOWNLOAD_PATH = 6
|
||||||
|
""" Dowload path is invalid """
|
||||||
BIND_ERROR = 7
|
BIND_ERROR = 7
|
||||||
|
""" Bind error can occur on start, if it's impossible to find a port to bind torrent2http to """
|
||||||
POPEN_ERROR = 8
|
POPEN_ERROR = 8
|
||||||
|
""" Can't start torrent2http client, path to binary doesn't exist or can't be executed """
|
||||||
PROCESS_ERROR = 9
|
PROCESS_ERROR = 9
|
||||||
|
""" torrent2http client started but exited abnormally, may be conflict in startup options """
|
||||||
TIMEOUT = 10
|
TIMEOUT = 10
|
||||||
|
""" torrent2http not answered during specified timeout """
|
||||||
INVALID_FILE_INDEX = 11
|
INVALID_FILE_INDEX = 11
|
||||||
|
""" Specified file index is invalid, no file with specified index found in torrent """
|
||||||
|
|
||||||
def __init__(self, message, code=0, **kwargs):
|
def __init__(self, message, code=0, **kwargs):
|
||||||
self.message = message
|
self.message = message
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
def can_bind(host, port):
|
||||||
|
"""
|
||||||
|
Checks we can bind to specified host and port
|
||||||
|
|
||||||
|
:param host: Host
|
||||||
|
:param port: Port
|
||||||
|
:return: True if bind succeed
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.bind((host, port))
|
||||||
|
s.close()
|
||||||
|
except socket.error:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def find_free_port(host):
|
||||||
|
"""
|
||||||
|
Finds free TCP port that can be used for binding
|
||||||
|
|
||||||
|
:param host: Host
|
||||||
|
:return: Free port
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
s.bind((host, 0))
|
||||||
|
port = s.getsockname()[1]
|
||||||
|
s.close()
|
||||||
|
except socket.error:
|
||||||
|
return False
|
||||||
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_fs_encoding(string):
|
||||||
|
if isinstance(string, str):
|
||||||
|
string = string.decode('utf-8')
|
||||||
|
return string.encode(sys.getfilesystemencoding() or 'utf-8')
|
Loading…
Reference in New Issue