Added code comments, better support of unicode path names
This commit is contained in:
		
							parent
							
								
									10d91a1d2d
								
							
						
					
					
						commit
						7fcf3e20ab
					
				
							
								
								
									
										60
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							@ -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"?>
 | 
			
		||||
<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>
 | 
			
		||||
        <import addon="xbmc.python" version="2.14.0"/>
 | 
			
		||||
    </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]
 | 
			
		||||
+ Torrent2http binaries updated to 1.0.1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -7,17 +7,22 @@ import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import urllib2
 | 
			
		||||
import httplib
 | 
			
		||||
from os.path import dirname
 | 
			
		||||
 | 
			
		||||
import logpipe
 | 
			
		||||
import mimetypes
 | 
			
		||||
import xbmc
 | 
			
		||||
 | 
			
		||||
from error import Error
 | 
			
		||||
from platform import Platform
 | 
			
		||||
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:
 | 
			
		||||
    """
 | 
			
		||||
    This is python binding class to torrent2http client.
 | 
			
		||||
    """
 | 
			
		||||
    SUBTITLES_FORMATS = ['.aqt', '.gsub', '.jss', '.sub', '.ttxt', '.pjs', '.psb', '.rt', '.smi', '.stl',
 | 
			
		||||
                         '.ssf', '.srt', '.ssa', '.ass', '.usf', '.idx']
 | 
			
		||||
 | 
			
		||||
@ -42,6 +47,12 @@ class Engine:
 | 
			
		||||
            xbmc.log("[torrent2http] %s" % message)
 | 
			
		||||
 | 
			
		||||
    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_dir = os.path.join(binaries_path, "%s_%s" % (self.platform.system, self.platform.arch))
 | 
			
		||||
        binary_path = os.path.join(binary_dir, binary)
 | 
			
		||||
@ -74,27 +85,6 @@ class Engine:
 | 
			
		||||
        self._log("Selected %s as torrent2http binary" % 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=".",
 | 
			
		||||
                 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,
 | 
			
		||||
@ -105,6 +95,55 @@ class Engine:
 | 
			
		||||
                 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,
 | 
			
		||||
                 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.trackers = trackers or []
 | 
			
		||||
        self.max_failcount = max_failcount
 | 
			
		||||
@ -154,21 +193,36 @@ class Engine:
 | 
			
		||||
 | 
			
		||||
    @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)
 | 
			
		||||
        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(path):
 | 
			
		||||
        if not os.path.isdir(ensure_fs_encoding(path)):
 | 
			
		||||
            raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH)
 | 
			
		||||
        return path
 | 
			
		||||
 | 
			
		||||
    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()
 | 
			
		||||
        binary_path = self._get_binary_path(self.binaries_path)
 | 
			
		||||
        download_path = self._validate_save_path(self.download_path)
 | 
			
		||||
        if not self._can_bind(self.bind_host, self.bind_port):
 | 
			
		||||
            port = self._find_free_port(self.bind_host)
 | 
			
		||||
        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 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))
 | 
			
		||||
@ -225,10 +279,8 @@ class Engine:
 | 
			
		||||
                        args.append("%s=false" % k)
 | 
			
		||||
                else:
 | 
			
		||||
                    args.append(k)
 | 
			
		||||
                    if isinstance(v, str):
 | 
			
		||||
                        v = v.decode('utf-8')
 | 
			
		||||
                    if isinstance(v, unicode):
 | 
			
		||||
                        v = v.encode(sys.getfilesystemencoding() or 'utf-8')
 | 
			
		||||
                    if isinstance(v, str) or isinstance(v, unicode):
 | 
			
		||||
                        v = ensure_fs_encoding(v)
 | 
			
		||||
                    else:
 | 
			
		||||
                        v = str(v)
 | 
			
		||||
                    args.append(v)
 | 
			
		||||
@ -266,12 +318,25 @@ class Engine:
 | 
			
		||||
        self._log("torrent2http successfully started.")
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
            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: torrent2http client request timeout
 | 
			
		||||
        """
 | 
			
		||||
        status = self._decode(self._request('status', timeout))
 | 
			
		||||
        status = SessionStatus(**status)
 | 
			
		||||
        return status
 | 
			
		||||
@ -293,6 +358,16 @@ class Engine:
 | 
			
		||||
                return MediaType.UNKNOWN
 | 
			
		||||
 | 
			
		||||
    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']
 | 
			
		||||
        if files:
 | 
			
		||||
            res = [FileStatus(index=index, media_type=self._detect_media_type(f['name']), **f)
 | 
			
		||||
@ -302,6 +377,16 @@ 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 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)
 | 
			
		||||
        if res:
 | 
			
		||||
            try:
 | 
			
		||||
@ -311,6 +396,13 @@ class Engine:
 | 
			
		||||
                            file_index=file_index)
 | 
			
		||||
 | 
			
		||||
    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']
 | 
			
		||||
        if peers:
 | 
			
		||||
            return [PeerInfo(**p) for p in peers]
 | 
			
		||||
@ -334,8 +426,8 @@ class Engine:
 | 
			
		||||
            if timeout is not None:
 | 
			
		||||
                kwargs['timeout'] = timeout
 | 
			
		||||
            return urllib2.urlopen(url, **kwargs).read()
 | 
			
		||||
        except urllib2.URLError as e:
 | 
			
		||||
            if isinstance(e.reason, socket.timeout):
 | 
			
		||||
        except (urllib2.URLError, httplib.HTTPException) as e:
 | 
			
		||||
            if isinstance(e, urllib2.URLError) and isinstance(e.reason, socket.timeout):
 | 
			
		||||
                raise Error("Timeout occurred while sending command '%s' to torrent2http" % cmd, Error.TIMEOUT)
 | 
			
		||||
            elif not self.is_alive() and self.started:
 | 
			
		||||
                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)
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    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:
 | 
			
		||||
            self.logpipe.close()
 | 
			
		||||
        if self.is_alive():
 | 
			
		||||
@ -371,4 +474,4 @@ class Engine:
 | 
			
		||||
                self.wait_on_close_timeout = None
 | 
			
		||||
        self.started = False
 | 
			
		||||
        self.logpipe = None
 | 
			
		||||
        self.process = None
 | 
			
		||||
        self.process = None
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,29 @@
 | 
			
		||||
 | 
			
		||||
class Error(Exception):
 | 
			
		||||
    TORRENT_ERROR = 12
 | 
			
		||||
    """ Error returned by libtorrent """
 | 
			
		||||
    CRASHED = 13
 | 
			
		||||
    """ torrent2http client has crashed abnormally """
 | 
			
		||||
    UNKNOWN_PLATFORM = 1
 | 
			
		||||
    """ Unknown/unsupported platform """
 | 
			
		||||
    XBMC_HOME_NOT_DEFINED = 2
 | 
			
		||||
    """ XBMC_HOME or KODI_HOME is not set """
 | 
			
		||||
    NOEXEC_FILESYSTEM = 3
 | 
			
		||||
    """ torrent2http binary is placed on noexec filesystem, so it can't be started """
 | 
			
		||||
    REQUEST_ERROR = 5
 | 
			
		||||
    """ Error occurred while sending request to torrent2http """
 | 
			
		||||
    INVALID_DOWNLOAD_PATH = 6
 | 
			
		||||
    """ Dowload path is invalid """
 | 
			
		||||
    BIND_ERROR = 7
 | 
			
		||||
    """ Bind error can occur on start, if it's impossible to find a port to bind torrent2http to """
 | 
			
		||||
    POPEN_ERROR = 8
 | 
			
		||||
    """ Can't start torrent2http client, path to binary doesn't exist or can't be executed """
 | 
			
		||||
    PROCESS_ERROR = 9
 | 
			
		||||
    """ torrent2http client started but exited abnormally, may be conflict in startup options """
 | 
			
		||||
    TIMEOUT = 10
 | 
			
		||||
    """ torrent2http not answered during specified timeout """
 | 
			
		||||
    INVALID_FILE_INDEX = 11
 | 
			
		||||
    """ Specified file index is invalid, no file with specified index found in torrent """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message, code=0, **kwargs):
 | 
			
		||||
        self.message = message
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										42
									
								
								lib/torrent2http/util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								lib/torrent2http/util.py
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user