Initial commit
This commit is contained in:
		
						commit
						74f247223f
					
				
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
.idea/
 | 
			
		||||
*.py[cod]
 | 
			
		||||
bin/
 | 
			
		||||
							
								
								
									
										16
									
								
								addon.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								addon.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 | 
			
		||||
<addon id="script.module.torrent2http" name="torrent2http" version="0.0.4" provider-name="anteo">
 | 
			
		||||
    <requires>
 | 
			
		||||
        <import addon="xbmc.python" version="2.14.0"/>
 | 
			
		||||
    </requires>
 | 
			
		||||
    <extension point="xbmc.python.module" library="lib"/>
 | 
			
		||||
    <extension point="xbmc.addon.metadata">
 | 
			
		||||
        <platform>all</platform>
 | 
			
		||||
        <language>en</language>
 | 
			
		||||
        <summary lang="en">Downloads torrents and share it over HTTP</summary>
 | 
			
		||||
        <summary lang="ru">Загружает торренты и раздает их по HTTP</summary>
 | 
			
		||||
        <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>anteo@academ.org</email>
 | 
			
		||||
    </extension>
 | 
			
		||||
</addon>
 | 
			
		||||
							
								
								
									
										8
									
								
								changelog.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								changelog.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
[B]Version 0.0.3[/B]
 | 
			
		||||
+ Detect media types
 | 
			
		||||
 | 
			
		||||
[B]Version 0.0.2[/B]
 | 
			
		||||
+ Added some configurable libtorrent options
 | 
			
		||||
 | 
			
		||||
[B]Version 0.0.1[/B]
 | 
			
		||||
+ Initial release
 | 
			
		||||
							
								
								
									
										44
									
								
								lib/torrent2http/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								lib/torrent2http/__init__.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
from collections import namedtuple
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# noinspection PyClassHasNoInit
 | 
			
		||||
class State:
 | 
			
		||||
    QUEUED_FOR_CHECKING = 0
 | 
			
		||||
    CHECKING_FILES = 1
 | 
			
		||||
    DOWNLOADING_METADATA = 2
 | 
			
		||||
    DOWNLOADING = 3
 | 
			
		||||
    FINISHED = 4
 | 
			
		||||
    SEEDING = 5
 | 
			
		||||
    ALLOCATING = 6
 | 
			
		||||
    CHECKING_RESUME_DATA = 7
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# noinspection PyClassHasNoInit
 | 
			
		||||
class MediaType:
 | 
			
		||||
    UNKNOWN = None
 | 
			
		||||
    AUDIO = 'audio'
 | 
			
		||||
    VIDEO = 'video'
 | 
			
		||||
    SUBTITLES = 'subtitles'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# noinspection PyClassHasNoInit
 | 
			
		||||
class Encryption:
 | 
			
		||||
    FORCED = 0
 | 
			
		||||
    ENABLED = 1
 | 
			
		||||
    DISABLED = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
SessionStatus = namedtuple('SessionStatus', "name, state, state_str, error, progress, download_rate, upload_rate, "
 | 
			
		||||
                                            "total_download, total_upload, num_peers, num_seeds, total_seeds, "
 | 
			
		||||
                                            "total_peers")
 | 
			
		||||
 | 
			
		||||
FileStatus = namedtuple('FileStatus', "name, save_path, url, size, offset, download, progress, index, media_type")
 | 
			
		||||
 | 
			
		||||
PeerInfo = namedtuple('PeerInfo', "ip, flags, source, up_speed, down_speed, total_upload, total_download, "
 | 
			
		||||
                                  "country, client")
 | 
			
		||||
 | 
			
		||||
from engine import Engine
 | 
			
		||||
from platform import Platform
 | 
			
		||||
from error import Error
 | 
			
		||||
							
								
								
									
										368
									
								
								lib/torrent2http/engine.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										368
									
								
								lib/torrent2http/engine.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,368 @@
 | 
			
		||||
# -*- coding: utf-8 -*-
 | 
			
		||||
import json
 | 
			
		||||
import os
 | 
			
		||||
import socket
 | 
			
		||||
import stat
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import urllib2
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Engine:
 | 
			
		||||
    SUBTITLES_FORMATS = ['.aqt', '.gsub', '.jss', '.sub', '.ttxt', '.pjs', '.psb', '.rt', '.smi', '.stl',
 | 
			
		||||
                         '.ssf', '.srt', '.ssa', '.ass', '.usf', '.idx']
 | 
			
		||||
 | 
			
		||||
    def _ensure_binary_executable(self, path):
 | 
			
		||||
        st = os.stat(path)
 | 
			
		||||
        if not st.st_mode & stat.S_IEXEC:
 | 
			
		||||
            self._log("%s is not executable, trying to change its mode..." % path)
 | 
			
		||||
            os.chmod(path, st.st_mode | stat.S_IEXEC)
 | 
			
		||||
            st = os.stat(path)
 | 
			
		||||
            if st.st_mode & stat.S_IEXEC:
 | 
			
		||||
                self._log("Succeeded")
 | 
			
		||||
                return True
 | 
			
		||||
            else:
 | 
			
		||||
                self._log("Failed")
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
    
 | 
			
		||||
    def _log(self, message):
 | 
			
		||||
        if self.logger:
 | 
			
		||||
            self.logger(message)
 | 
			
		||||
        else:
 | 
			
		||||
            xbmc.log("[torrent2http] %s" % message)
 | 
			
		||||
 | 
			
		||||
    def _get_binary_path(self, binaries_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)
 | 
			
		||||
        if not os.path.isfile(binary_path):
 | 
			
		||||
            raise Error("Can't find torrent2http binary for %s" % self.platform,
 | 
			
		||||
                        Error.UNKNOWN_PLATFORM, platform=str(self.platform))
 | 
			
		||||
 | 
			
		||||
        if not self._ensure_binary_executable(binary_path):
 | 
			
		||||
            if self.platform.system == "android":
 | 
			
		||||
                self._log("Trying to copy torrent2http to ext4, since the sdcard is noexec...")
 | 
			
		||||
                xbmc_home = os.environ.get('XBMC_HOME') or os.environ.get('KODI_HOME')
 | 
			
		||||
                if not xbmc_home:
 | 
			
		||||
                    raise Error("Suppose we are running XBMC, but environment variable "
 | 
			
		||||
                                "XBMC_HOME or KODI_HOME is not found", Error.XBMC_HOME_NOT_DEFINED)
 | 
			
		||||
                base_xbmc_path = dirname(dirname(dirname(xbmc_home)))
 | 
			
		||||
                android_binary_dir = os.path.join(base_xbmc_path, "files")
 | 
			
		||||
                if not os.path.exists(android_binary_dir):
 | 
			
		||||
                    os.makedirs(android_binary_dir)
 | 
			
		||||
                android_binary_path = os.path.join(android_binary_dir, binary)
 | 
			
		||||
                if not os.path.exists(android_binary_path) or \
 | 
			
		||||
                        int(os.path.getmtime(android_binary_path)) < int(os.path.getmtime(binary_path)):
 | 
			
		||||
                    import shutil
 | 
			
		||||
                    shutil.copy2(binary_path, android_binary_path)
 | 
			
		||||
                    if not self._ensure_binary_executable(android_binary_path):
 | 
			
		||||
                        raise Error("Can't make %s executable" % android_binary_path, Error.NOEXEC_FILESYSTEM)
 | 
			
		||||
                binary_path = android_binary_path
 | 
			
		||||
            else:
 | 
			
		||||
                raise Error("Can't make %s executable, ensure it's placed on exec partition and "
 | 
			
		||||
                            "partition is in read/write mode" % binary_path, Error.NOEXEC_FILESYSTEM)
 | 
			
		||||
        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,
 | 
			
		||||
                 log_stats=False, encryption=Encryption.ENABLED, keep_complete=False, keep_incomplete=False,
 | 
			
		||||
                 keep_files=False, log_files_progress=False, log_overall_progress=False, log_pieces_progress=False,
 | 
			
		||||
                 listen_port=6881, use_random_port=False, max_idle_timeout=None, no_sparse=False, resume_file=None,
 | 
			
		||||
                 user_agent=None, startup_timeout=5, state_file=None, enable_utp=True, enable_tcp=True,
 | 
			
		||||
                 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):
 | 
			
		||||
        self.dht_routers = dht_routers or []
 | 
			
		||||
        self.trackers = trackers or []
 | 
			
		||||
        self.max_failcount = max_failcount
 | 
			
		||||
        self.min_reconnect_time = min_reconnect_time
 | 
			
		||||
        self.request_timeout = request_timeout
 | 
			
		||||
        self.peer_connect_timeout = peer_connect_timeout
 | 
			
		||||
        self.connection_speed = connection_speed
 | 
			
		||||
        self.torrent_connect_boost = torrent_connect_boost
 | 
			
		||||
        self.platform = platform
 | 
			
		||||
        self.bind_host = bind_host
 | 
			
		||||
        self.bind_port = bind_port
 | 
			
		||||
        self.binaries_path = binaries_path or os.path.join(dirname(dirname(dirname(os.path.abspath(__file__)))), 'bin')
 | 
			
		||||
        self.download_path = download_path
 | 
			
		||||
        self.connections_limit = connections_limit
 | 
			
		||||
        self.download_kbps = download_kbps
 | 
			
		||||
        self.upload_kbps = upload_kbps
 | 
			
		||||
        self.enable_dht = enable_dht
 | 
			
		||||
        self.enable_lsd = enable_lsd
 | 
			
		||||
        self.enable_natpmp = enable_natpmp
 | 
			
		||||
        self.enable_upnp = enable_upnp
 | 
			
		||||
        self.enable_scrape = enable_scrape
 | 
			
		||||
        self.log_stats = log_stats
 | 
			
		||||
        self.encryption = encryption
 | 
			
		||||
        self.keep_complete = keep_complete
 | 
			
		||||
        self.keep_incomplete = keep_incomplete
 | 
			
		||||
        self.keep_files = keep_files
 | 
			
		||||
        self.log_files_progress = log_files_progress
 | 
			
		||||
        self.log_overall_progress = log_overall_progress
 | 
			
		||||
        self.log_pieces_progress = log_pieces_progress
 | 
			
		||||
        self.listen_port = listen_port
 | 
			
		||||
        self.use_random_port = use_random_port
 | 
			
		||||
        self.max_idle_timeout = max_idle_timeout
 | 
			
		||||
        self.no_sparse = no_sparse
 | 
			
		||||
        self.resume_file = resume_file
 | 
			
		||||
        self.user_agent = user_agent
 | 
			
		||||
        self.startup_timeout = startup_timeout
 | 
			
		||||
        self.state_file = state_file
 | 
			
		||||
        self.wait_on_close_timeout = None
 | 
			
		||||
        self.enable_utp = enable_utp
 | 
			
		||||
        self.enable_tcp = enable_tcp
 | 
			
		||||
        self.debug_alerts = debug_alerts
 | 
			
		||||
        self.logger = logger
 | 
			
		||||
        self.uri = uri
 | 
			
		||||
        self.logpipe = None
 | 
			
		||||
        self.process = None
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _validate_save_path(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):
 | 
			
		||||
            raise Error("Download path doesn't exist (%s)" % path, Error.INVALID_DOWNLOAD_PATH)
 | 
			
		||||
        return path
 | 
			
		||||
 | 
			
		||||
    def start(self, start_index=None):
 | 
			
		||||
        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 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))
 | 
			
		||||
            self.bind_port = port
 | 
			
		||||
 | 
			
		||||
        kwargs = {
 | 
			
		||||
            '--bind': "%s:%s" % (self.bind_host, self.bind_port),
 | 
			
		||||
            '--uri': self.uri,
 | 
			
		||||
            '--file-index': start_index,
 | 
			
		||||
            '--dl-path': download_path,
 | 
			
		||||
            '--connections-limit': self.connections_limit,
 | 
			
		||||
            '--dl-rate': self.download_kbps,
 | 
			
		||||
            '--ul-rate': self.upload_kbps,
 | 
			
		||||
            '--enable-dht': self.enable_dht,
 | 
			
		||||
            '--enable-lsd': self.enable_lsd,
 | 
			
		||||
            '--enable-natpmp': self.enable_natpmp,
 | 
			
		||||
            '--enable-upnp': self.enable_upnp,
 | 
			
		||||
            '--enable-scrape': self.enable_scrape,
 | 
			
		||||
            '--encryption': self.encryption,
 | 
			
		||||
            '--show-stats': self.log_stats,
 | 
			
		||||
            '--files-progress': self.log_files_progress,
 | 
			
		||||
            '--overall-progress': self.log_overall_progress,
 | 
			
		||||
            '--pieces-progress': self.log_pieces_progress,
 | 
			
		||||
            '--listen-port': self.listen_port,
 | 
			
		||||
            '--random-port': self.use_random_port,
 | 
			
		||||
            '--keep-complete': self.keep_complete,
 | 
			
		||||
            '--keep-incomplete': self.keep_incomplete,
 | 
			
		||||
            '--keep-files': self.keep_files,
 | 
			
		||||
            '--max-idle': self.max_idle_timeout,
 | 
			
		||||
            '--no-sparse': self.no_sparse,
 | 
			
		||||
            '--resume-file': self.resume_file,
 | 
			
		||||
            '--user-agent': self.user_agent,
 | 
			
		||||
            '--state-file': self.state_file,
 | 
			
		||||
            '--enable-utp': self.enable_utp,
 | 
			
		||||
            '--enable-tcp': self.enable_tcp,
 | 
			
		||||
            '--debug-alerts': self.debug_alerts,
 | 
			
		||||
            '--torrent-connect-boost': self.torrent_connect_boost,
 | 
			
		||||
            '--connection-speed': self.connection_speed,
 | 
			
		||||
            '--peer-connect-timeout': self.peer_connect_timeout,
 | 
			
		||||
            '--request-timeout': self.request_timeout,
 | 
			
		||||
            '--min-reconnect-time': self.min_reconnect_time,
 | 
			
		||||
            '--max-failcount': self.max_failcount,
 | 
			
		||||
            '--dht-routers': ",".join(self.dht_routers),
 | 
			
		||||
            '--trackers': ",".join(self.trackers),
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        args = [binary_path]
 | 
			
		||||
        for k, v in kwargs.iteritems():
 | 
			
		||||
            if v is not None:
 | 
			
		||||
                if isinstance(v, bool):
 | 
			
		||||
                    if v:
 | 
			
		||||
                        args.append(k)
 | 
			
		||||
                    else:
 | 
			
		||||
                        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')
 | 
			
		||||
                    else:
 | 
			
		||||
                        v = str(v)
 | 
			
		||||
                    args.append(v)
 | 
			
		||||
 | 
			
		||||
        self._log("Invoking %s" % " ".join(args))
 | 
			
		||||
        startupinfo = None
 | 
			
		||||
        if self.platform.system == "windows":
 | 
			
		||||
            startupinfo = subprocess.STARTUPINFO()
 | 
			
		||||
            startupinfo.dwFlags |= 1
 | 
			
		||||
            startupinfo.wShowWindow = 0
 | 
			
		||||
 | 
			
		||||
        self.logpipe = logpipe.LogPipe(self._log)
 | 
			
		||||
        try:
 | 
			
		||||
            self.process = subprocess.Popen(args, stderr=self.logpipe, stdout=self.logpipe, startupinfo=startupinfo)
 | 
			
		||||
        except OSError, e:
 | 
			
		||||
            raise Error("Can't start torrent2http: %r" % e, Error.POPEN_ERROR)
 | 
			
		||||
 | 
			
		||||
        start = time.time()
 | 
			
		||||
        initialized = False
 | 
			
		||||
        while (time.time() - start) < self.startup_timeout:
 | 
			
		||||
            time.sleep(0.1)
 | 
			
		||||
            if not self.is_alive():
 | 
			
		||||
                raise Error("Can't start torrent2http, see log for details", Error.PROCESS_ERROR)
 | 
			
		||||
            try:
 | 
			
		||||
                self.status(1)
 | 
			
		||||
                initialized = True
 | 
			
		||||
                break
 | 
			
		||||
            except Error:
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
        if not initialized:
 | 
			
		||||
            raise Error("Can't start torrent2http, time is out", Error.TIMEOUT)
 | 
			
		||||
        self._log("torrent2http successfully started.")
 | 
			
		||||
 | 
			
		||||
    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):
 | 
			
		||||
        status = self._decode(self._request('status', timeout))
 | 
			
		||||
        status = SessionStatus(**status)
 | 
			
		||||
        return status
 | 
			
		||||
 | 
			
		||||
    def _detect_media_type(self, name):
 | 
			
		||||
        ext = os.path.splitext(name)[1]
 | 
			
		||||
        if ext in self.SUBTITLES_FORMATS:
 | 
			
		||||
            return MediaType.SUBTITLES
 | 
			
		||||
        else:
 | 
			
		||||
            mime_type = mimetypes.guess_type(name)[0]
 | 
			
		||||
            if not mime_type:
 | 
			
		||||
                return MediaType.UNKNOWN
 | 
			
		||||
            mime_type = mime_type.split("/")[0]
 | 
			
		||||
            if mime_type == 'audio':
 | 
			
		||||
                return MediaType.AUDIO
 | 
			
		||||
            elif mime_type == 'video':
 | 
			
		||||
                return MediaType.VIDEO
 | 
			
		||||
            else:
 | 
			
		||||
                return MediaType.UNKNOWN
 | 
			
		||||
 | 
			
		||||
    def list(self, media_types=None, timeout=10):
 | 
			
		||||
        files = self._decode(self._request('ls', timeout))['files']
 | 
			
		||||
        if files:
 | 
			
		||||
            res = [FileStatus(index=index, media_type=self._detect_media_type(f['name']), **f)
 | 
			
		||||
                   for index, f in enumerate(files)]
 | 
			
		||||
            if media_types is not None:
 | 
			
		||||
                res = filter(lambda fs: fs.media_type in media_types, res)
 | 
			
		||||
            return res
 | 
			
		||||
 | 
			
		||||
    def file_status(self, file_index, timeout=10):
 | 
			
		||||
        res = self.list(timeout=timeout)
 | 
			
		||||
        if res:
 | 
			
		||||
            try:
 | 
			
		||||
                return next((f for f in res if f.index == file_index))
 | 
			
		||||
            except StopIteration:
 | 
			
		||||
                raise Error("Requested file index (%d) is invalid" % file_index, Error.INVALID_FILE_INDEX,
 | 
			
		||||
                            file_index=file_index)
 | 
			
		||||
 | 
			
		||||
    def peers(self, timeout=10):
 | 
			
		||||
        peers = self._decode(self._request('peers', timeout))['peers']
 | 
			
		||||
        if peers:
 | 
			
		||||
            return [PeerInfo(**p) for p in peers]
 | 
			
		||||
 | 
			
		||||
    def is_alive(self):
 | 
			
		||||
        return self.process and self.process.poll() is None
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _decode(response):
 | 
			
		||||
        try:
 | 
			
		||||
            return json.loads(response)
 | 
			
		||||
        except (KeyError, ValueError), e:
 | 
			
		||||
            raise Error("Can't decode response from torrent2http: %r" % e, Error.REQUEST_ERROR)
 | 
			
		||||
 | 
			
		||||
    def _request(self, cmd, timeout=None):
 | 
			
		||||
        if not self.is_alive():
 | 
			
		||||
            raise Error("torrent2http is not started", Error.REQUEST_ERROR)
 | 
			
		||||
        try:
 | 
			
		||||
            url = "http://%s:%s/%s" % (self.bind_host, self.bind_port, cmd)
 | 
			
		||||
            kwargs = {}
 | 
			
		||||
            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):
 | 
			
		||||
                raise Error("Timeout occurred while sending command '%s' to torrent2http" % cmd, Error.TIMEOUT)
 | 
			
		||||
            else:
 | 
			
		||||
                raise Error("Can't send command '%s' to torrent2http: %r" % (cmd, e), Error.REQUEST_ERROR)
 | 
			
		||||
        except socket.error as e:
 | 
			
		||||
            reason = e[1] if isinstance(e, tuple) else e
 | 
			
		||||
            raise Error("Can't read from torrent2http: %s" % reason, Error.REQUEST_ERROR)
 | 
			
		||||
 | 
			
		||||
    def wait_on_close(self, wait_timeout=10):
 | 
			
		||||
        self.wait_on_close_timeout = wait_timeout
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        if self.logpipe and self.wait_on_close_timeout is None:
 | 
			
		||||
            self.logpipe.close()
 | 
			
		||||
        if self.is_alive():
 | 
			
		||||
            self._log("Shutting down torrent2http...")
 | 
			
		||||
            self._request('shutdown')
 | 
			
		||||
            finished = False
 | 
			
		||||
            if self.wait_on_close_timeout is not None:
 | 
			
		||||
                start = time.time()
 | 
			
		||||
                os.close(self.logpipe.write_fd)
 | 
			
		||||
                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("Timeout occurred while shutting down torrent2http, killing it")
 | 
			
		||||
                    self.process.kill()
 | 
			
		||||
                else:
 | 
			
		||||
                    self._log("torrent2http successfully shut down.")
 | 
			
		||||
                self.wait_on_close_timeout = None
 | 
			
		||||
        self.logpipe = None
 | 
			
		||||
        self.process = None
 | 
			
		||||
							
								
								
									
										21
									
								
								lib/torrent2http/error.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/torrent2http/error.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
 | 
			
		||||
class Error(Exception):
 | 
			
		||||
    TORRENT_ERROR = 12
 | 
			
		||||
    UNKNOWN_PLATFORM = 1
 | 
			
		||||
    XBMC_HOME_NOT_DEFINED = 2
 | 
			
		||||
    NOEXEC_FILESYSTEM = 3
 | 
			
		||||
    REQUEST_ERROR = 5
 | 
			
		||||
    INVALID_DOWNLOAD_PATH = 6
 | 
			
		||||
    BIND_ERROR = 7
 | 
			
		||||
    POPEN_ERROR = 8
 | 
			
		||||
    PROCESS_ERROR = 9
 | 
			
		||||
    TIMEOUT = 10
 | 
			
		||||
    INVALID_FILE_INDEX = 11
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message, code=0, **kwargs):
 | 
			
		||||
        self.message = message
 | 
			
		||||
        self.code = code
 | 
			
		||||
        self.kwargs = kwargs
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.message
 | 
			
		||||
							
								
								
									
										33
									
								
								lib/torrent2http/logpipe.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/torrent2http/logpipe.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
import os
 | 
			
		||||
import threading
 | 
			
		||||
import re
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LogPipe(threading.Thread):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, logger):
 | 
			
		||||
        threading.Thread.__init__(self)
 | 
			
		||||
        self.daemon = False
 | 
			
		||||
        self.logger = logger
 | 
			
		||||
        self.read_fd, self.write_fd = os.pipe()
 | 
			
		||||
        self.stop = threading.Event()
 | 
			
		||||
        self.start()
 | 
			
		||||
 | 
			
		||||
    def fileno(self):
 | 
			
		||||
        return self.write_fd
 | 
			
		||||
 | 
			
		||||
    def run(self):
 | 
			
		||||
        self.logger("Logging thread started.")
 | 
			
		||||
        with os.fdopen(self.read_fd) as f:
 | 
			
		||||
            for line in iter(f.readline, ""):
 | 
			
		||||
                line = re.sub(r'^\d+/\d+/\d+ \d+:\d+:\d+ ', '', line)
 | 
			
		||||
                self.logger(line.strip())
 | 
			
		||||
                if self.stop.is_set():
 | 
			
		||||
                    break
 | 
			
		||||
        self.logger("Logging thread finished.")
 | 
			
		||||
 | 
			
		||||
    def close(self):
 | 
			
		||||
        self.stop.set()
 | 
			
		||||
        f = os.fdopen(self.write_fd, "w")
 | 
			
		||||
        f.write("Stopping logging thread...\n")
 | 
			
		||||
        f.close()
 | 
			
		||||
							
								
								
									
										613
									
								
								lib/torrent2http/mimetypes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										613
									
								
								lib/torrent2http/mimetypes.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,613 @@
 | 
			
		||||
"""Guess the MIME type of a file.
 | 
			
		||||
 | 
			
		||||
This module defines two useful functions:
 | 
			
		||||
 | 
			
		||||
guess_type(url, strict=1) -- guess the MIME type and encoding of a URL.
 | 
			
		||||
 | 
			
		||||
guess_extension(type, strict=1) -- guess the extension for a given MIME type.
 | 
			
		||||
 | 
			
		||||
It also contains the following, for tuning the behavior:
 | 
			
		||||
 | 
			
		||||
Data:
 | 
			
		||||
 | 
			
		||||
knownfiles -- list of files to parse
 | 
			
		||||
inited -- flag set when init() has been called
 | 
			
		||||
suffix_map -- dictionary mapping suffixes to suffixes
 | 
			
		||||
encodings_map -- dictionary mapping suffixes to encodings
 | 
			
		||||
types_map -- dictionary mapping suffixes to types
 | 
			
		||||
 | 
			
		||||
Functions:
 | 
			
		||||
 | 
			
		||||
init([files]) -- parse a list of files, default knownfiles (on Windows, the
 | 
			
		||||
  default values are taken from the registry)
 | 
			
		||||
read_mime_types(file) -- parse one file, return a dictionary or None
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import posixpath
 | 
			
		||||
import urllib
 | 
			
		||||
try:
 | 
			
		||||
    import _winreg
 | 
			
		||||
except ImportError:
 | 
			
		||||
    _winreg = None
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    "guess_type","guess_extension","guess_all_extensions",
 | 
			
		||||
    "add_type","read_mime_types","init"
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
knownfiles = [
 | 
			
		||||
    "/etc/mime.types",
 | 
			
		||||
    "/etc/httpd/mime.types",                    # Mac OS X
 | 
			
		||||
    "/etc/httpd/conf/mime.types",               # Apache
 | 
			
		||||
    "/etc/apache/mime.types",                   # Apache 1
 | 
			
		||||
    "/etc/apache2/mime.types",                  # Apache 2
 | 
			
		||||
    "/usr/local/etc/httpd/conf/mime.types",
 | 
			
		||||
    "/usr/local/lib/netscape/mime.types",
 | 
			
		||||
    "/usr/local/etc/httpd/conf/mime.types",     # Apache 1.2
 | 
			
		||||
    "/usr/local/etc/mime.types",                # Apache 1.3
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
inited = False
 | 
			
		||||
_db = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MimeTypes:
 | 
			
		||||
    """MIME-types datastore.
 | 
			
		||||
 | 
			
		||||
    This datastore can handle information from mime.types-style files
 | 
			
		||||
    and supports basic determination of MIME type from a filename or
 | 
			
		||||
    URL, and can guess a reasonable extension given a MIME type.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, filenames=(), strict=True):
 | 
			
		||||
        if not inited:
 | 
			
		||||
            init()
 | 
			
		||||
        self.encodings_map = encodings_map.copy()
 | 
			
		||||
        self.suffix_map = suffix_map.copy()
 | 
			
		||||
        self.types_map = ({}, {}) # dict for (non-strict, strict)
 | 
			
		||||
        self.types_map_inv = ({}, {})
 | 
			
		||||
        for (ext, type) in types_map.items():
 | 
			
		||||
            self.add_type(type, ext, True)
 | 
			
		||||
        for (ext, type) in common_types.items():
 | 
			
		||||
            self.add_type(type, ext, False)
 | 
			
		||||
        for name in filenames:
 | 
			
		||||
            self.read(name, strict)
 | 
			
		||||
 | 
			
		||||
    def add_type(self, type, ext, strict=True):
 | 
			
		||||
        """Add a mapping between a type and an extension.
 | 
			
		||||
 | 
			
		||||
        When the extension is already known, the new
 | 
			
		||||
        type will replace the old one. When the type
 | 
			
		||||
        is already known the extension will be added
 | 
			
		||||
        to the list of known extensions.
 | 
			
		||||
 | 
			
		||||
        If strict is true, information will be added to
 | 
			
		||||
        list of standard types, else to the list of non-standard
 | 
			
		||||
        types.
 | 
			
		||||
        """
 | 
			
		||||
        self.types_map[strict][ext] = type
 | 
			
		||||
        exts = self.types_map_inv[strict].setdefault(type, [])
 | 
			
		||||
        if ext not in exts:
 | 
			
		||||
            exts.append(ext)
 | 
			
		||||
 | 
			
		||||
    def guess_type(self, url, strict=True):
 | 
			
		||||
        """Guess the type of a file based on its URL.
 | 
			
		||||
 | 
			
		||||
        Return value is a tuple (type, encoding) where type is None if
 | 
			
		||||
        the type can't be guessed (no or unknown suffix) or a string
 | 
			
		||||
        of the form type/subtype, usable for a MIME Content-type
 | 
			
		||||
        header; and encoding is None for no encoding or the name of
 | 
			
		||||
        the program used to encode (e.g. compress or gzip).  The
 | 
			
		||||
        mappings are table driven.  Encoding suffixes are case
 | 
			
		||||
        sensitive; type suffixes are first tried case sensitive, then
 | 
			
		||||
        case insensitive.
 | 
			
		||||
 | 
			
		||||
        The suffixes .tgz, .taz and .tz (case sensitive!) are all
 | 
			
		||||
        mapped to '.tar.gz'.  (This is table-driven too, using the
 | 
			
		||||
        dictionary suffix_map.)
 | 
			
		||||
 | 
			
		||||
        Optional `strict' argument when False adds a bunch of commonly found,
 | 
			
		||||
        but non-standard types.
 | 
			
		||||
        """
 | 
			
		||||
        scheme, url = urllib.splittype(url)
 | 
			
		||||
        if scheme == 'data':
 | 
			
		||||
            # syntax of data URLs:
 | 
			
		||||
            # dataurl   := "data:" [ mediatype ] [ ";base64" ] "," data
 | 
			
		||||
            # mediatype := [ type "/" subtype ] *( ";" parameter )
 | 
			
		||||
            # data      := *urlchar
 | 
			
		||||
            # parameter := attribute "=" value
 | 
			
		||||
            # type/subtype defaults to "text/plain"
 | 
			
		||||
            comma = url.find(',')
 | 
			
		||||
            if comma < 0:
 | 
			
		||||
                # bad data URL
 | 
			
		||||
                return None, None
 | 
			
		||||
            semi = url.find(';', 0, comma)
 | 
			
		||||
            if semi >= 0:
 | 
			
		||||
                type = url[:semi]
 | 
			
		||||
            else:
 | 
			
		||||
                type = url[:comma]
 | 
			
		||||
            if '=' in type or '/' not in type:
 | 
			
		||||
                type = 'text/plain'
 | 
			
		||||
            return type, None           # never compressed, so encoding is None
 | 
			
		||||
        base, ext = posixpath.splitext(url)
 | 
			
		||||
        while ext in self.suffix_map:
 | 
			
		||||
            base, ext = posixpath.splitext(base + self.suffix_map[ext])
 | 
			
		||||
        if ext in self.encodings_map:
 | 
			
		||||
            encoding = self.encodings_map[ext]
 | 
			
		||||
            base, ext = posixpath.splitext(base)
 | 
			
		||||
        else:
 | 
			
		||||
            encoding = None
 | 
			
		||||
        types_map = self.types_map[True]
 | 
			
		||||
        if ext in types_map:
 | 
			
		||||
            return types_map[ext], encoding
 | 
			
		||||
        elif ext.lower() in types_map:
 | 
			
		||||
            return types_map[ext.lower()], encoding
 | 
			
		||||
        elif strict:
 | 
			
		||||
            return None, encoding
 | 
			
		||||
        types_map = self.types_map[False]
 | 
			
		||||
        if ext in types_map:
 | 
			
		||||
            return types_map[ext], encoding
 | 
			
		||||
        elif ext.lower() in types_map:
 | 
			
		||||
            return types_map[ext.lower()], encoding
 | 
			
		||||
        else:
 | 
			
		||||
            return None, encoding
 | 
			
		||||
 | 
			
		||||
    def guess_all_extensions(self, type, strict=True):
 | 
			
		||||
        """Guess the extensions for a file based on its MIME type.
 | 
			
		||||
 | 
			
		||||
        Return value is a list of strings giving the possible filename
 | 
			
		||||
        extensions, including the leading dot ('.').  The extension is not
 | 
			
		||||
        guaranteed to have been associated with any particular data stream,
 | 
			
		||||
        but would be mapped to the MIME type `type' by guess_type().
 | 
			
		||||
 | 
			
		||||
        Optional `strict' argument when false adds a bunch of commonly found,
 | 
			
		||||
        but non-standard types.
 | 
			
		||||
        """
 | 
			
		||||
        type = type.lower()
 | 
			
		||||
        extensions = self.types_map_inv[True].get(type, [])
 | 
			
		||||
        if not strict:
 | 
			
		||||
            for ext in self.types_map_inv[False].get(type, []):
 | 
			
		||||
                if ext not in extensions:
 | 
			
		||||
                    extensions.append(ext)
 | 
			
		||||
        return extensions
 | 
			
		||||
 | 
			
		||||
    def guess_extension(self, type, strict=True):
 | 
			
		||||
        """Guess the extension for a file based on its MIME type.
 | 
			
		||||
 | 
			
		||||
        Return value is a string giving a filename extension,
 | 
			
		||||
        including the leading dot ('.').  The extension is not
 | 
			
		||||
        guaranteed to have been associated with any particular data
 | 
			
		||||
        stream, but would be mapped to the MIME type `type' by
 | 
			
		||||
        guess_type().  If no extension can be guessed for `type', None
 | 
			
		||||
        is returned.
 | 
			
		||||
 | 
			
		||||
        Optional `strict' argument when false adds a bunch of commonly found,
 | 
			
		||||
        but non-standard types.
 | 
			
		||||
        """
 | 
			
		||||
        extensions = self.guess_all_extensions(type, strict)
 | 
			
		||||
        if not extensions:
 | 
			
		||||
            return None
 | 
			
		||||
        return extensions[0]
 | 
			
		||||
 | 
			
		||||
    def read(self, filename, strict=True):
 | 
			
		||||
        """
 | 
			
		||||
        Read a single mime.types-format file, specified by pathname.
 | 
			
		||||
 | 
			
		||||
        If strict is true, information will be added to
 | 
			
		||||
        list of standard types, else to the list of non-standard
 | 
			
		||||
        types.
 | 
			
		||||
        """
 | 
			
		||||
        with open(filename) as fp:
 | 
			
		||||
            self.readfp(fp, strict)
 | 
			
		||||
 | 
			
		||||
    def readfp(self, fp, strict=True):
 | 
			
		||||
        """
 | 
			
		||||
        Read a single mime.types-format file.
 | 
			
		||||
 | 
			
		||||
        If strict is true, information will be added to
 | 
			
		||||
        list of standard types, else to the list of non-standard
 | 
			
		||||
        types.
 | 
			
		||||
        """
 | 
			
		||||
        while 1:
 | 
			
		||||
            line = fp.readline()
 | 
			
		||||
            if not line:
 | 
			
		||||
                break
 | 
			
		||||
            words = line.split()
 | 
			
		||||
            for i in range(len(words)):
 | 
			
		||||
                if words[i][0] == '#':
 | 
			
		||||
                    del words[i:]
 | 
			
		||||
                    break
 | 
			
		||||
            if not words:
 | 
			
		||||
                continue
 | 
			
		||||
            type, suffixes = words[0], words[1:]
 | 
			
		||||
            for suff in suffixes:
 | 
			
		||||
                self.add_type(type, '.' + suff, strict)
 | 
			
		||||
 | 
			
		||||
    def read_windows_registry(self, strict=True):
 | 
			
		||||
        """
 | 
			
		||||
        Load the MIME types database from Windows registry.
 | 
			
		||||
 | 
			
		||||
        If strict is true, information will be added to
 | 
			
		||||
        list of standard types, else to the list of non-standard
 | 
			
		||||
        types.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        # Windows only
 | 
			
		||||
        if not _winreg:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        def enum_types(mimedb):
 | 
			
		||||
            i = 0
 | 
			
		||||
            while True:
 | 
			
		||||
                try:
 | 
			
		||||
                    ctype = _winreg.EnumKey(mimedb, i)
 | 
			
		||||
                except EnvironmentError:
 | 
			
		||||
                    break
 | 
			
		||||
                try:
 | 
			
		||||
                    ctype = ctype.encode(default_encoding) # omit in 3.x!
 | 
			
		||||
                except UnicodeEncodeError:
 | 
			
		||||
                    pass
 | 
			
		||||
                else:
 | 
			
		||||
                    yield ctype
 | 
			
		||||
                i += 1
 | 
			
		||||
 | 
			
		||||
        default_encoding = sys.getdefaultencoding()
 | 
			
		||||
        with _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT,
 | 
			
		||||
                             r'MIME\Database\Content Type') as mimedb:
 | 
			
		||||
            for ctype in enum_types(mimedb):
 | 
			
		||||
                try:
 | 
			
		||||
                    with _winreg.OpenKey(mimedb, ctype) as key:
 | 
			
		||||
                        suffix, datatype = _winreg.QueryValueEx(key,
 | 
			
		||||
                                                                'Extension')
 | 
			
		||||
                except EnvironmentError:
 | 
			
		||||
                    continue
 | 
			
		||||
                if datatype != _winreg.REG_SZ:
 | 
			
		||||
                    continue
 | 
			
		||||
                try:
 | 
			
		||||
                    suffix = suffix.encode(default_encoding) # omit in 3.x!
 | 
			
		||||
                except UnicodeEncodeError:
 | 
			
		||||
                    continue
 | 
			
		||||
                self.add_type(ctype, suffix, strict)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def guess_type(url, strict=True):
 | 
			
		||||
    """Guess the type of a file based on its URL.
 | 
			
		||||
 | 
			
		||||
    Return value is a tuple (type, encoding) where type is None if the
 | 
			
		||||
    type can't be guessed (no or unknown suffix) or a string of the
 | 
			
		||||
    form type/subtype, usable for a MIME Content-type header; and
 | 
			
		||||
    encoding is None for no encoding or the name of the program used
 | 
			
		||||
    to encode (e.g. compress or gzip).  The mappings are table
 | 
			
		||||
    driven.  Encoding suffixes are case sensitive; type suffixes are
 | 
			
		||||
    first tried case sensitive, then case insensitive.
 | 
			
		||||
 | 
			
		||||
    The suffixes .tgz, .taz and .tz (case sensitive!) are all mapped
 | 
			
		||||
    to ".tar.gz".  (This is table-driven too, using the dictionary
 | 
			
		||||
    suffix_map).
 | 
			
		||||
 | 
			
		||||
    Optional `strict' argument when false adds a bunch of commonly found, but
 | 
			
		||||
    non-standard types.
 | 
			
		||||
    """
 | 
			
		||||
    if _db is None:
 | 
			
		||||
        init()
 | 
			
		||||
    return _db.guess_type(url, strict)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def guess_all_extensions(type, strict=True):
 | 
			
		||||
    """Guess the extensions for a file based on its MIME type.
 | 
			
		||||
 | 
			
		||||
    Return value is a list of strings giving the possible filename
 | 
			
		||||
    extensions, including the leading dot ('.').  The extension is not
 | 
			
		||||
    guaranteed to have been associated with any particular data
 | 
			
		||||
    stream, but would be mapped to the MIME type `type' by
 | 
			
		||||
    guess_type().  If no extension can be guessed for `type', None
 | 
			
		||||
    is returned.
 | 
			
		||||
 | 
			
		||||
    Optional `strict' argument when false adds a bunch of commonly found,
 | 
			
		||||
    but non-standard types.
 | 
			
		||||
    """
 | 
			
		||||
    if _db is None:
 | 
			
		||||
        init()
 | 
			
		||||
    return _db.guess_all_extensions(type, strict)
 | 
			
		||||
 | 
			
		||||
def guess_extension(type, strict=True):
 | 
			
		||||
    """Guess the extension for a file based on its MIME type.
 | 
			
		||||
 | 
			
		||||
    Return value is a string giving a filename extension, including the
 | 
			
		||||
    leading dot ('.').  The extension is not guaranteed to have been
 | 
			
		||||
    associated with any particular data stream, but would be mapped to the
 | 
			
		||||
    MIME type `type' by guess_type().  If no extension can be guessed for
 | 
			
		||||
    `type', None is returned.
 | 
			
		||||
 | 
			
		||||
    Optional `strict' argument when false adds a bunch of commonly found,
 | 
			
		||||
    but non-standard types.
 | 
			
		||||
    """
 | 
			
		||||
    if _db is None:
 | 
			
		||||
        init()
 | 
			
		||||
    return _db.guess_extension(type, strict)
 | 
			
		||||
 | 
			
		||||
def add_type(type, ext, strict=True):
 | 
			
		||||
    """Add a mapping between a type and an extension.
 | 
			
		||||
 | 
			
		||||
    When the extension is already known, the new
 | 
			
		||||
    type will replace the old one. When the type
 | 
			
		||||
    is already known the extension will be added
 | 
			
		||||
    to the list of known extensions.
 | 
			
		||||
 | 
			
		||||
    If strict is true, information will be added to
 | 
			
		||||
    list of standard types, else to the list of non-standard
 | 
			
		||||
    types.
 | 
			
		||||
    """
 | 
			
		||||
    if _db is None:
 | 
			
		||||
        init()
 | 
			
		||||
    return _db.add_type(type, ext, strict)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def init(files=None):
 | 
			
		||||
    global suffix_map, types_map, encodings_map, common_types
 | 
			
		||||
    global inited, _db
 | 
			
		||||
    inited = True    # so that MimeTypes.__init__() doesn't call us again
 | 
			
		||||
    db = MimeTypes()
 | 
			
		||||
    if files is None:
 | 
			
		||||
        if _winreg:
 | 
			
		||||
            db.read_windows_registry()
 | 
			
		||||
        files = knownfiles
 | 
			
		||||
    for file in files:
 | 
			
		||||
        if os.path.isfile(file):
 | 
			
		||||
            db.read(file)
 | 
			
		||||
    encodings_map = db.encodings_map
 | 
			
		||||
    suffix_map = db.suffix_map
 | 
			
		||||
    types_map = db.types_map[True]
 | 
			
		||||
    common_types = db.types_map[False]
 | 
			
		||||
    # Make the DB a global variable now that it is fully initialized
 | 
			
		||||
    _db = db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def read_mime_types(file):
 | 
			
		||||
    try:
 | 
			
		||||
        f = open(file)
 | 
			
		||||
    except IOError:
 | 
			
		||||
        return None
 | 
			
		||||
    db = MimeTypes()
 | 
			
		||||
    db.readfp(f, True)
 | 
			
		||||
    return db.types_map[True]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _default_mime_types():
 | 
			
		||||
    global suffix_map
 | 
			
		||||
    global encodings_map
 | 
			
		||||
    global types_map
 | 
			
		||||
    global common_types
 | 
			
		||||
 | 
			
		||||
    suffix_map = {
 | 
			
		||||
        '.tgz': '.tar.gz',
 | 
			
		||||
        '.taz': '.tar.gz',
 | 
			
		||||
        '.tz': '.tar.gz',
 | 
			
		||||
        '.tbz2': '.tar.bz2',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    encodings_map = {
 | 
			
		||||
        '.gz': 'gzip',
 | 
			
		||||
        '.Z': 'compress',
 | 
			
		||||
        '.bz2': 'bzip2',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    # Before adding new types, make sure they are either registered with IANA,
 | 
			
		||||
    # at http://www.isi.edu/in-notes/iana/assignments/media-types
 | 
			
		||||
    # or extensions, i.e. using the x- prefix
 | 
			
		||||
 | 
			
		||||
    # If you add to these, please keep them sorted!
 | 
			
		||||
    types_map = {
 | 
			
		||||
        '.3gp'    : 'video/3gp',
 | 
			
		||||
        '.a'      : 'application/octet-stream',
 | 
			
		||||
        '.ai'     : 'application/postscript',
 | 
			
		||||
        '.aif'    : 'audio/x-aiff',
 | 
			
		||||
        '.aifc'   : 'audio/x-aiff',
 | 
			
		||||
        '.aiff'   : 'audio/x-aiff',
 | 
			
		||||
        '.asf'    : 'video/x-ms-asf',
 | 
			
		||||
        '.asx'    : 'video/x-ms-asf',
 | 
			
		||||
        '.au'     : 'audio/basic',
 | 
			
		||||
        '.avi'    : 'video/x-msvideo',
 | 
			
		||||
        '.axv'    : 'video/annodex',
 | 
			
		||||
        '.bat'    : 'text/plain',
 | 
			
		||||
        '.bcpio'  : 'application/x-bcpio',
 | 
			
		||||
        '.bin'    : 'application/octet-stream',
 | 
			
		||||
        '.bmp'    : 'image/x-ms-bmp',
 | 
			
		||||
        '.c'      : 'text/plain',
 | 
			
		||||
        # Duplicates :(
 | 
			
		||||
        '.cdf'    : 'application/x-cdf',
 | 
			
		||||
        '.cdf'    : 'application/x-netcdf',
 | 
			
		||||
        '.cpio'   : 'application/x-cpio',
 | 
			
		||||
        '.csh'    : 'application/x-csh',
 | 
			
		||||
        '.css'    : 'text/css',
 | 
			
		||||
        '.dif'    : 'video/dv',
 | 
			
		||||
        '.dl'     : 'video/dl',
 | 
			
		||||
        '.dll'    : 'application/octet-stream',
 | 
			
		||||
        '.dv'     : 'video/dv',
 | 
			
		||||
        '.doc'    : 'application/msword',
 | 
			
		||||
        '.dot'    : 'application/msword',
 | 
			
		||||
        '.dvi'    : 'application/x-dvi',
 | 
			
		||||
        '.eml'    : 'message/rfc822',
 | 
			
		||||
        '.eps'    : 'application/postscript',
 | 
			
		||||
        '.etx'    : 'text/x-setext',
 | 
			
		||||
        '.exe'    : 'application/octet-stream',
 | 
			
		||||
        '.fli'    : 'video/fli',
 | 
			
		||||
        '.flv'    : 'video/x-flv',
 | 
			
		||||
        '.gif'    : 'image/gif',
 | 
			
		||||
        '.gl'     : 'video/gl',
 | 
			
		||||
        '.gtar'   : 'application/x-gtar',
 | 
			
		||||
        '.h'      : 'text/plain',
 | 
			
		||||
        '.hdf'    : 'application/x-hdf',
 | 
			
		||||
        '.htm'    : 'text/html',
 | 
			
		||||
        '.html'   : 'text/html',
 | 
			
		||||
        '.ief'    : 'image/ief',
 | 
			
		||||
        '.jpe'    : 'image/jpeg',
 | 
			
		||||
        '.jpeg'   : 'image/jpeg',
 | 
			
		||||
        '.jpg'    : 'image/jpeg',
 | 
			
		||||
        '.js'     : 'application/x-javascript',
 | 
			
		||||
        '.ksh'    : 'text/plain',
 | 
			
		||||
        '.latex'  : 'application/x-latex',
 | 
			
		||||
        '.lsf'    : 'video/x-la-lsf',
 | 
			
		||||
        '.lsx'    : 'video/x-la-lsf',
 | 
			
		||||
        '.m1v'    : 'video/mpeg',
 | 
			
		||||
        '.man'    : 'application/x-troff-man',
 | 
			
		||||
        '.me'     : 'application/x-troff-me',
 | 
			
		||||
        '.mht'    : 'message/rfc822',
 | 
			
		||||
        '.mhtml'  : 'message/rfc822',
 | 
			
		||||
        '.mif'    : 'application/x-mif',
 | 
			
		||||
        '.mng'    : 'video/x-mng',
 | 
			
		||||
        '.movie'  : 'video/x-sgi-movie',
 | 
			
		||||
        '.mp2'    : 'audio/mpeg',
 | 
			
		||||
        '.mp3'    : 'audio/mpeg',
 | 
			
		||||
        '.mp4'    : 'video/mp4',
 | 
			
		||||
        '.mpa'    : 'video/mpeg',
 | 
			
		||||
        '.mpe'    : 'video/mpeg',
 | 
			
		||||
        '.mpeg'   : 'video/mpeg',
 | 
			
		||||
        '.mpg'    : 'video/mpeg',
 | 
			
		||||
        '.mpv'    : 'video/matroska',
 | 
			
		||||
        '.mkv'    : 'video/matroska',
 | 
			
		||||
        '.mov'    : 'video/quicktime',
 | 
			
		||||
        '.ms'     : 'application/x-troff-ms',
 | 
			
		||||
        '.nc'     : 'application/x-netcdf',
 | 
			
		||||
        '.nws'    : 'message/rfc822',
 | 
			
		||||
        '.o'      : 'application/octet-stream',
 | 
			
		||||
        '.obj'    : 'application/octet-stream',
 | 
			
		||||
        '.oda'    : 'application/oda',
 | 
			
		||||
        '.ogv'    : 'video/ogg',
 | 
			
		||||
        '.p12'    : 'application/x-pkcs12',
 | 
			
		||||
        '.p7c'    : 'application/pkcs7-mime',
 | 
			
		||||
        '.pbm'    : 'image/x-portable-bitmap',
 | 
			
		||||
        '.pdf'    : 'application/pdf',
 | 
			
		||||
        '.pfx'    : 'application/x-pkcs12',
 | 
			
		||||
        '.pgm'    : 'image/x-portable-graymap',
 | 
			
		||||
        '.pl'     : 'text/plain',
 | 
			
		||||
        '.png'    : 'image/png',
 | 
			
		||||
        '.pnm'    : 'image/x-portable-anymap',
 | 
			
		||||
        '.pot'    : 'application/vnd.ms-powerpoint',
 | 
			
		||||
        '.ppa'    : 'application/vnd.ms-powerpoint',
 | 
			
		||||
        '.ppm'    : 'image/x-portable-pixmap',
 | 
			
		||||
        '.pps'    : 'application/vnd.ms-powerpoint',
 | 
			
		||||
        '.ppt'    : 'application/vnd.ms-powerpoint',
 | 
			
		||||
        '.ps'     : 'application/postscript',
 | 
			
		||||
        '.pwz'    : 'application/vnd.ms-powerpoint',
 | 
			
		||||
        '.py'     : 'text/x-python',
 | 
			
		||||
        '.pyc'    : 'application/x-python-code',
 | 
			
		||||
        '.pyo'    : 'application/x-python-code',
 | 
			
		||||
        '.qt'     : 'video/quicktime',
 | 
			
		||||
        '.ra'     : 'audio/x-pn-realaudio',
 | 
			
		||||
        '.ram'    : 'application/x-pn-realaudio',
 | 
			
		||||
        '.ras'    : 'image/x-cmu-raster',
 | 
			
		||||
        '.rdf'    : 'application/xml',
 | 
			
		||||
        '.rgb'    : 'image/x-rgb',
 | 
			
		||||
        '.roff'   : 'application/x-troff',
 | 
			
		||||
        '.rtx'    : 'text/richtext',
 | 
			
		||||
        '.sgm'    : 'text/x-sgml',
 | 
			
		||||
        '.sgml'   : 'text/x-sgml',
 | 
			
		||||
        '.sh'     : 'application/x-sh',
 | 
			
		||||
        '.shar'   : 'application/x-shar',
 | 
			
		||||
        '.snd'    : 'audio/basic',
 | 
			
		||||
        '.so'     : 'application/octet-stream',
 | 
			
		||||
        '.src'    : 'application/x-wais-source',
 | 
			
		||||
        '.sv4cpio': 'application/x-sv4cpio',
 | 
			
		||||
        '.sv4crc' : 'application/x-sv4crc',
 | 
			
		||||
        '.swf'    : 'application/x-shockwave-flash',
 | 
			
		||||
        '.t'      : 'application/x-troff',
 | 
			
		||||
        '.tar'    : 'application/x-tar',
 | 
			
		||||
        '.tcl'    : 'application/x-tcl',
 | 
			
		||||
        '.tex'    : 'application/x-tex',
 | 
			
		||||
        '.texi'   : 'application/x-texinfo',
 | 
			
		||||
        '.texinfo': 'application/x-texinfo',
 | 
			
		||||
        '.tif'    : 'image/tiff',
 | 
			
		||||
        '.tiff'   : 'image/tiff',
 | 
			
		||||
        '.tr'     : 'application/x-troff',
 | 
			
		||||
        '.ts'     : 'video/MP2T',
 | 
			
		||||
        '.tsv'    : 'text/tab-separated-values',
 | 
			
		||||
        '.txt'    : 'text/plain',
 | 
			
		||||
        '.ustar'  : 'application/x-ustar',
 | 
			
		||||
        '.vcf'    : 'text/x-vcard',
 | 
			
		||||
        '.wav'    : 'audio/x-wav',
 | 
			
		||||
        '.webm'   : 'video/webm',
 | 
			
		||||
        '.wiz'    : 'application/msword',
 | 
			
		||||
        '.wm'     : 'video/x-ms-wm',
 | 
			
		||||
        '.wmv'    : 'video/x-ms-wmv',
 | 
			
		||||
        '.wmx'    : 'video/x-ms-wmx',
 | 
			
		||||
        '.wvx'    : 'video/x-ms-wvx',
 | 
			
		||||
        '.wsdl'   : 'application/xml',
 | 
			
		||||
        '.xbm'    : 'image/x-xbitmap',
 | 
			
		||||
        '.xlb'    : 'application/vnd.ms-excel',
 | 
			
		||||
        # Duplicates :(
 | 
			
		||||
        '.xls'    : 'application/excel',
 | 
			
		||||
        '.xls'    : 'application/vnd.ms-excel',
 | 
			
		||||
        '.xml'    : 'text/xml',
 | 
			
		||||
        '.xpdl'   : 'application/xml',
 | 
			
		||||
        '.xpm'    : 'image/x-xpixmap',
 | 
			
		||||
        '.xsl'    : 'application/xml',
 | 
			
		||||
        '.xwd'    : 'image/x-xwindowdump',
 | 
			
		||||
        '.zip'    : 'application/zip',
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    # These are non-standard types, commonly found in the wild.  They will
 | 
			
		||||
    # only match if strict=0 flag is given to the API methods.
 | 
			
		||||
 | 
			
		||||
    # Please sort these too
 | 
			
		||||
    common_types = {
 | 
			
		||||
        '.jpg' : 'image/jpg',
 | 
			
		||||
        '.mid' : 'audio/midi',
 | 
			
		||||
        '.midi': 'audio/midi',
 | 
			
		||||
        '.pct' : 'image/pict',
 | 
			
		||||
        '.pic' : 'image/pict',
 | 
			
		||||
        '.pict': 'image/pict',
 | 
			
		||||
        '.rtf' : 'application/rtf',
 | 
			
		||||
        '.xul' : 'text/xul'
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_default_mime_types()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    import getopt
 | 
			
		||||
 | 
			
		||||
    USAGE = """\
 | 
			
		||||
Usage: mimetypes.py [options] type
 | 
			
		||||
 | 
			
		||||
Options:
 | 
			
		||||
    --help / -h       -- print this message and exit
 | 
			
		||||
    --lenient / -l    -- additionally search of some common, but non-standard
 | 
			
		||||
                         types.
 | 
			
		||||
    --extension / -e  -- guess extension instead of type
 | 
			
		||||
 | 
			
		||||
More than one type argument may be given.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
    def usage(code, msg=''):
 | 
			
		||||
        print USAGE
 | 
			
		||||
        if msg: print msg
 | 
			
		||||
        sys.exit(code)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        opts, args = getopt.getopt(sys.argv[1:], 'hle',
 | 
			
		||||
                                   ['help', 'lenient', 'extension'])
 | 
			
		||||
    except getopt.error, msg:
 | 
			
		||||
        usage(1, msg)
 | 
			
		||||
 | 
			
		||||
    strict = 1
 | 
			
		||||
    extension = 0
 | 
			
		||||
    for opt, arg in opts:
 | 
			
		||||
        if opt in ('-h', '--help'):
 | 
			
		||||
            usage(0)
 | 
			
		||||
        elif opt in ('-l', '--lenient'):
 | 
			
		||||
            strict = 0
 | 
			
		||||
        elif opt in ('-e', '--extension'):
 | 
			
		||||
            extension = 1
 | 
			
		||||
    for gtype in args:
 | 
			
		||||
        if extension:
 | 
			
		||||
            guess = guess_extension(gtype, strict)
 | 
			
		||||
            if not guess: print "I don't know anything about type", gtype
 | 
			
		||||
            else: print guess
 | 
			
		||||
        else:
 | 
			
		||||
            guess, encoding = guess_type(gtype, strict)
 | 
			
		||||
            if not guess: print "I don't know anything about type", gtype
 | 
			
		||||
            else: print 'type:', guess, 'encoding:', encoding
 | 
			
		||||
							
								
								
									
										38
									
								
								lib/torrent2http/platform.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/torrent2http/platform.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
from __future__ import absolute_import
 | 
			
		||||
from platform import uname
 | 
			
		||||
from .error import Error
 | 
			
		||||
import sys
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Platform:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.arch = self.arch()
 | 
			
		||||
        self.system = self.system()
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return "%s/%s" % (self.system, self.arch)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def arch():
 | 
			
		||||
        if uname()[4].startswith('arm'):
 | 
			
		||||
            return 'arm'
 | 
			
		||||
        elif sys.maxsize > 2**32:
 | 
			
		||||
            return 'x64'
 | 
			
		||||
        else:
 | 
			
		||||
            return 'x86'
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def system():
 | 
			
		||||
        if sys.platform.startswith('linux'):
 | 
			
		||||
            if 'ANDROID_DATA' in os.environ:
 | 
			
		||||
                return 'android'
 | 
			
		||||
            else:
 | 
			
		||||
                return 'linux'
 | 
			
		||||
        elif sys.platform.startswith('win'):
 | 
			
		||||
            return 'windows'
 | 
			
		||||
        elif sys.platform.startswith('darwin'):
 | 
			
		||||
            return 'darwin'
 | 
			
		||||
        else:
 | 
			
		||||
            raise Error("Platform %s is unknown" % sys.platform, Error.UNKNOWN_PLATFORM,
 | 
			
		||||
                                    platform=sys.platform)
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user