parent
f39749807f
commit
6e04f3fc14
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||||
<addon id="script.module.pyrrent2http" name="pyrrent2http" version="1.0.0" provider-name="inpos">
|
<addon id="script.module.pyrrent2http" name="pyrrent2http" version="1.1.0" provider-name="inpos">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="2.14.0"/>
|
<import addon="xbmc.python" version="3.0.0"/>
|
||||||
<import addon="script.module.libtorrent" />
|
<import addon="script.module.libtorrent" />
|
||||||
<import addon="script.module.chardet" />
|
<import addon="script.module.chardet" />
|
||||||
</requires>
|
</requires>
|
||||||
|
@ -17,5 +17,5 @@
|
||||||
<description lang="ru">Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent.</description>
|
<description lang="ru">Обеспечивает последовательную (sequential) загрузку торрентов для потокового онлайн просмотра через HTTP. Основан на библиотеке LibTorrent.</description>
|
||||||
<description lang="en">Provides sequential torrent downloading for online streaming video and other media over HTTP.</description>
|
<description lang="en">Provides sequential torrent downloading for online streaming video and other media over HTTP.</description>
|
||||||
<email>inpos@yandex.ru</email>
|
<email>inpos@yandex.ru</email>
|
||||||
<source>https://github.com/inpos/script.module.pyrrent2http</source></extension>
|
<source>https://git.ukamnya.ru/ukamnya/script.module.pyrrent2http</source></extension>
|
||||||
</addon>
|
</addon>
|
||||||
|
|
|
@ -3,33 +3,6 @@
|
||||||
from collections import namedtuple
|
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, "
|
SessionStatus = namedtuple('SessionStatus', "name, state, state_str, error, progress, download_rate, upload_rate, "
|
||||||
"total_download, total_upload, num_peers, num_seeds, total_seeds, "
|
"total_download, total_upload, num_peers, num_seeds, total_seeds, "
|
||||||
"total_peers")
|
"total_peers")
|
||||||
|
@ -39,5 +12,5 @@ FileStatus = namedtuple('FileStatus', "name, save_path, url, size, offset, downl
|
||||||
PeerInfo = namedtuple('PeerInfo', "ip, flags, source, up_speed, down_speed, total_upload, total_download, "
|
PeerInfo = namedtuple('PeerInfo', "ip, flags, source, up_speed, down_speed, total_upload, total_download, "
|
||||||
"country, client")
|
"country, client")
|
||||||
|
|
||||||
from engine import Engine
|
from .engine import Engine
|
||||||
from error import Error
|
from .error import Error
|
||||||
|
|
|
@ -1,31 +1,37 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
|
import threading
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import chardet
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import pyrrent2http
|
|
||||||
import xbmc
|
import xbmc
|
||||||
from error import Error
|
|
||||||
from . import SessionStatus, FileStatus, PeerInfo, Encryption
|
from . import SessionStatus, FileStatus, PeerInfo
|
||||||
from util import can_bind, find_free_port, localize_path, uri2path, detect_media_type
|
from . import pyrrent2http
|
||||||
import threading
|
from .error import Error
|
||||||
import urllib
|
from .structs import Encryption
|
||||||
import chardet
|
from .util import can_bind, find_free_port, localize_path, uri2path, detect_media_type
|
||||||
|
|
||||||
LOGGING = True
|
LOGGING = True
|
||||||
|
|
||||||
|
|
||||||
class Engine:
|
class Engine:
|
||||||
"""
|
"""
|
||||||
This is python binding class to pyrrent2http client.
|
This is python binding class to pyrrent2http client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def _log(self, message):
|
def _log(self, message):
|
||||||
if self.logger:
|
if self.logger:
|
||||||
self.logger(message)
|
self.logger(message)
|
||||||
else:
|
else:
|
||||||
xbmc.log("[pyrrent2http] %s" % message)
|
xbmc.log("[pyrrent2http] %s" % message)
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, uri=None, platform=None, download_path=".",
|
def __init__(self, uri=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=200, download_kbps=-1, upload_kbps=-1,
|
||||||
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,
|
||||||
log_stats=False, encryption=Encryption.ENABLED, keep_complete=False, keep_incomplete=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,
|
keep_files=False, log_files_progress=False, log_overall_progress=False, log_pieces_progress=False,
|
||||||
|
@ -198,7 +204,7 @@ class Engine:
|
||||||
'maxFailCount': self.max_failcount,
|
'maxFailCount': self.max_failcount,
|
||||||
'showPiecesProgress': self.log_pieces_progress,
|
'showPiecesProgress': self.log_pieces_progress,
|
||||||
'idleTimeout': self.max_idle_timeout,
|
'idleTimeout': self.max_idle_timeout,
|
||||||
#'fileIndex': start_index,
|
# 'fileIndex': start_index,
|
||||||
'connectionsLimit': self.connections_limit,
|
'connectionsLimit': self.connections_limit,
|
||||||
'enableScrape': self.enable_scrape,
|
'enableScrape': self.enable_scrape,
|
||||||
'enableUTP': self.enable_utp,
|
'enableUTP': self.enable_utp,
|
||||||
|
@ -208,15 +214,19 @@ class Engine:
|
||||||
}
|
}
|
||||||
|
|
||||||
self._log("Invoking pyrrent2http")
|
self._log("Invoking pyrrent2http")
|
||||||
|
|
||||||
class Logging(object):
|
class Logging(object):
|
||||||
def __init__(self, _log):
|
def __init__(self, _log):
|
||||||
self._log = _log
|
self._log = _log
|
||||||
|
|
||||||
def info(self, message):
|
def info(self, message):
|
||||||
if LOGGING:
|
if LOGGING:
|
||||||
self._log('INFO: %s' % (message,))
|
self._log('INFO: %s' % (message,))
|
||||||
|
|
||||||
def error(self, message):
|
def error(self, message):
|
||||||
if LOGGING:
|
if LOGGING:
|
||||||
self._log('ERROR: %s' % (message,))
|
self._log('ERROR: %s' % (message,))
|
||||||
|
|
||||||
pyrrent2http.logging = Logging(self._log)
|
pyrrent2http.logging = Logging(self._log)
|
||||||
|
|
||||||
self.pyrrent2http = pyrrent2http.Pyrrent2http(**kwargs)
|
self.pyrrent2http = pyrrent2http.Pyrrent2http(**kwargs)
|
||||||
|
@ -224,10 +234,9 @@ class Engine:
|
||||||
self.pyrrent2http.startServices()
|
self.pyrrent2http.startServices()
|
||||||
self.pyrrent2http.addTorrent()
|
self.pyrrent2http.addTorrent()
|
||||||
self.pyrrent2http.startHTTP()
|
self.pyrrent2http.startHTTP()
|
||||||
self.pyrrent2http_loop = threading.Thread(target = self.pyrrent2http.loop)
|
self.pyrrent2http_loop = threading.Thread(target=self.pyrrent2http.loop)
|
||||||
self.pyrrent2http_loop.start()
|
self.pyrrent2http_loop.start()
|
||||||
|
|
||||||
|
|
||||||
start = time.time()
|
start = time.time()
|
||||||
self.started = True
|
self.started = True
|
||||||
initialized = False
|
initialized = False
|
||||||
|
@ -236,7 +245,7 @@ class Engine:
|
||||||
if not self.is_alive():
|
if not self.is_alive():
|
||||||
raise Error("Can't start pyrrent2http, see log for details", Error.PROCESS_ERROR)
|
raise Error("Can't start pyrrent2http, see log for details", Error.PROCESS_ERROR)
|
||||||
try:
|
try:
|
||||||
#self.status(1)
|
# self.status(1)
|
||||||
initialized = True
|
initialized = True
|
||||||
break
|
break
|
||||||
except Error:
|
except Error:
|
||||||
|
@ -252,6 +261,7 @@ class Engine:
|
||||||
|
|
||||||
def pause(self):
|
def pause(self):
|
||||||
self.pyrrent2http.pause = True
|
self.pyrrent2http.pause = True
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
self.pyrrent2http.pause = False
|
self.pyrrent2http.pause = False
|
||||||
|
|
||||||
|
@ -279,8 +289,6 @@ class Engine:
|
||||||
status = SessionStatus(**status)
|
status = SessionStatus(**status)
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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).
|
Returns list of files in the torrent (see FileStatus named tuple).
|
||||||
|
@ -296,8 +304,9 @@ class Engine:
|
||||||
if files:
|
if files:
|
||||||
res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
|
res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
|
||||||
if media_types is not None:
|
if media_types is not None:
|
||||||
res = filter(lambda fs: fs.media_type in media_types, res)
|
res = [fs for fs in res if fs.media_type in media_types]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def list_from_info(self, media_types=None):
|
def list_from_info(self, media_types=None):
|
||||||
try:
|
try:
|
||||||
info = pyrrent2http.lt.torrent_info(uri2path(self.uri))
|
info = pyrrent2http.lt.torrent_info(uri2path(self.uri))
|
||||||
|
@ -306,21 +315,21 @@ class Engine:
|
||||||
files = []
|
files = []
|
||||||
for i in range(info.num_files()):
|
for i in range(info.num_files()):
|
||||||
f = info.file_at(i)
|
f = info.file_at(i)
|
||||||
Url = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.quote(f.path)
|
Url = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.parse.quote(f.path)
|
||||||
files.append({
|
files.append({
|
||||||
'name': localize_path(f.path),
|
'name': localize_path(f.path),
|
||||||
'size': f.size,
|
'size': f.size,
|
||||||
'offset': f.offset,
|
'offset': f.offset,
|
||||||
'media_type': media_types and detect_media_type(f.path.decode(chardet.detect(f.path)['encoding'])) or '',
|
'media_type': media_types is not None and detect_media_type(f.path) or '',
|
||||||
'download': 0,
|
'download': 0,
|
||||||
'progress': 0.0,
|
'progress': 0.0,
|
||||||
'save_path': '',
|
'save_path': '',
|
||||||
'url': Url
|
'url': Url
|
||||||
})
|
})
|
||||||
if files:
|
if len(files) > 0:
|
||||||
res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
|
res = [FileStatus(index=index, **f) for index, f in enumerate(files)]
|
||||||
if media_types is not None:
|
if media_types is not None:
|
||||||
res = filter(lambda fs: fs.media_type in media_types, res)
|
res = [fs for fs in res if fs.media_type in media_types]
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def file_status(self, file_index, timeout=10):
|
def file_status(self, file_index, timeout=10):
|
||||||
|
@ -355,6 +364,7 @@ class Engine:
|
||||||
|
|
||||||
def is_alive(self):
|
def is_alive(self):
|
||||||
return self.pyrrent2http_loop.is_alive()
|
return self.pyrrent2http_loop.is_alive()
|
||||||
|
|
||||||
def wait_on_close(self, wait_timeout=10):
|
def wait_on_close(self, wait_timeout=10):
|
||||||
"""
|
"""
|
||||||
By default, close() method sends shutdown command to pyrrent2http, stops logging and returns immediately, not
|
By default, close() method sends shutdown command to pyrrent2http, stops logging and returns immediately, not
|
||||||
|
|
|
@ -1,613 +0,0 @@
|
||||||
"""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
|
|
|
@ -4,10 +4,11 @@ import chardet
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from python_libtorrent import get_libtorrent # @UnresolvedImport
|
from python_libtorrent import get_libtorrent # @UnresolvedImport
|
||||||
lt=get_libtorrent()
|
|
||||||
print('Imported libtorrent v%s from python_libtorrent' %(lt.version, ))
|
lt = get_libtorrent()
|
||||||
except Exception, e:
|
print(('Imported libtorrent v%s from python_libtorrent' % (lt.version,)))
|
||||||
print('Error importing python_libtorrent.Exception: %s' %(str(e),))
|
except Exception as e:
|
||||||
|
print(('Error importing python_libtorrent.Exception: %s' % (str(e),)))
|
||||||
try:
|
try:
|
||||||
import libtorrent as lt # @UnresolvedImport
|
import libtorrent as lt # @UnresolvedImport
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -17,17 +18,16 @@ except Exception, e:
|
||||||
|
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib.request, urllib.parse, urllib.error
|
||||||
import BaseHTTPServer
|
import http.server
|
||||||
import SocketServer
|
import socketserver
|
||||||
import threading
|
import threading
|
||||||
import io
|
import io
|
||||||
from util import localize_path, Struct, detect_media_type, uri2path, encode_msg
|
from .util import localize_path, Struct, detect_media_type, uri2path, encode_msg
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if os.getenv('ANDROID_ROOT'):
|
if os.getenv('ANDROID_ROOT'):
|
||||||
from ctypes import *
|
from ctypes import *
|
||||||
|
|
||||||
libc = CDLL('/system/lib/libc.so')
|
libc = CDLL('/system/lib/libc.so')
|
||||||
libc.lseek64.restype = c_ulonglong
|
libc.lseek64.restype = c_ulonglong
|
||||||
libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint]
|
libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint]
|
||||||
|
@ -41,7 +41,7 @@ if os.getenv('ANDROID_ROOT'):
|
||||||
if not hasattr(os, 'getppid'):
|
if not hasattr(os, 'getppid'):
|
||||||
import ctypes
|
import ctypes
|
||||||
|
|
||||||
TH32CS_SNAPPROCESS = 0x02L
|
TH32CS_SNAPPROCESS = 0x02
|
||||||
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable
|
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable
|
||||||
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @UndefinedVariable
|
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @UndefinedVariable
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ if not hasattr(os, 'getppid'):
|
||||||
_kernel32dll = ctypes.windll.Kernel32
|
_kernel32dll = ctypes.windll.Kernel32
|
||||||
CloseHandle = _kernel32dll.CloseHandle
|
CloseHandle = _kernel32dll.CloseHandle
|
||||||
|
|
||||||
|
|
||||||
class PROCESSENTRY32(ctypes.Structure):
|
class PROCESSENTRY32(ctypes.Structure):
|
||||||
_fields_ = [
|
_fields_ = [
|
||||||
("dwSize", ctypes.c_ulong),
|
("dwSize", ctypes.c_ulong),
|
||||||
|
@ -65,9 +66,11 @@ if not hasattr(os, 'getppid'):
|
||||||
("szExeFile", ctypes.c_wchar * MAX_PATH)
|
("szExeFile", ctypes.c_wchar * MAX_PATH)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
Process32First = _kernel32dll.Process32FirstW
|
Process32First = _kernel32dll.Process32FirstW
|
||||||
Process32Next = _kernel32dll.Process32NextW
|
Process32Next = _kernel32dll.Process32NextW
|
||||||
|
|
||||||
|
|
||||||
def getppid():
|
def getppid():
|
||||||
'''
|
'''
|
||||||
:return: The pid of the parent of this process.
|
:return: The pid of the parent of this process.
|
||||||
|
@ -93,18 +96,21 @@ if not hasattr(os, 'getppid'):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
os.getppid = getppid
|
os.getppid = getppid
|
||||||
|
|
||||||
#################################################################################
|
#################################################################################
|
||||||
|
|
||||||
AVOID_HTTP_SERVER_EXCEPTION_OUTPUT = True
|
AVOID_HTTP_SERVER_EXCEPTION_OUTPUT = True
|
||||||
VERSION = "0.6.0"
|
VERSION = "0.6.0"
|
||||||
#USER_AGENT = "pyrrent2http/" + VERSION + " libtorrent/" + lt.version
|
# USER_AGENT = "pyrrent2http/" + VERSION + " libtorrent/" + lt.version
|
||||||
USER_AGENT = 'libtorrent/1.0.9.0'
|
USER_AGENT = 'libtorrent/1.0.9.0'
|
||||||
|
|
||||||
VIDEO_EXTS={'.avi':'video/x-msvideo','.mp4':'video/mp4','.mkv':'video/x-matroska',
|
VIDEO_EXTS = {'.avi': 'video/x-msvideo', '.mp4': 'video/mp4', '.mkv': 'video/x-matroska',
|
||||||
'.m4v':'video/mp4','.mov':'video/quicktime', '.mpg':'video/mpeg','.ogv':'video/ogg',
|
'.m4v': 'video/mp4', '.mov': 'video/quicktime', '.mpg': 'video/mpeg', '.ogv': 'video/ogg',
|
||||||
'.ogg':'video/ogg', '.webm':'video/webm', '.ts': 'video/mp2t', '.3gp':'video/3gpp'}
|
'.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp'}
|
||||||
|
|
||||||
|
|
||||||
######################################################################################
|
######################################################################################
|
||||||
|
|
||||||
class Ticker(object):
|
class Ticker(object):
|
||||||
|
@ -114,6 +120,7 @@ class Ticker(object):
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def true(self):
|
def true(self):
|
||||||
if self.tick:
|
if self.tick:
|
||||||
|
@ -137,6 +144,7 @@ class Ticker(object):
|
||||||
self._timer.cancel()
|
self._timer.cancel()
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
|
|
||||||
#######################################################################################
|
#######################################################################################
|
||||||
|
|
||||||
class TorrentFile(object):
|
class TorrentFile(object):
|
||||||
|
@ -149,11 +157,12 @@ class TorrentFile(object):
|
||||||
downloaded = 0
|
downloaded = 0
|
||||||
progress = 0.0
|
progress = 0.0
|
||||||
pdl_thread = None
|
pdl_thread = None
|
||||||
|
|
||||||
def __init__(self, tfs, fileEntry, savePath, index):
|
def __init__(self, tfs, fileEntry, savePath, index):
|
||||||
self.tfs = tfs
|
self.tfs = tfs
|
||||||
self.fileEntry = fileEntry
|
self.fileEntry = fileEntry
|
||||||
self.name = self.fileEntry.path
|
self.name = self.fileEntry.path
|
||||||
self.unicode_name = self.name.decode(chardet.detect(self.name)['encoding'])
|
self.unicode_name = isinstance(self.name, str) and self.name or self.name.decode(chardet.detect(self.name)['encoding'])
|
||||||
self.media_type = detect_media_type(self.unicode_name)
|
self.media_type = detect_media_type(self.unicode_name)
|
||||||
self.save_path = savePath
|
self.save_path = savePath
|
||||||
self.index = index
|
self.index = index
|
||||||
|
@ -165,8 +174,10 @@ class TorrentFile(object):
|
||||||
|
|
||||||
def Downloaded(self):
|
def Downloaded(self):
|
||||||
return self.downloaded
|
return self.downloaded
|
||||||
|
|
||||||
def Progress(self):
|
def Progress(self):
|
||||||
return self.progress
|
return self.progress
|
||||||
|
|
||||||
def __fileptr_(self):
|
def __fileptr_(self):
|
||||||
if self.closed:
|
if self.closed:
|
||||||
return None
|
return None
|
||||||
|
@ -180,35 +191,44 @@ class TorrentFile(object):
|
||||||
else:
|
else:
|
||||||
self.filePtr = io.open(self.save_path, 'rb')
|
self.filePtr = io.open(self.save_path, 'rb')
|
||||||
return self.filePtr
|
return self.filePtr
|
||||||
|
|
||||||
def log(self, message):
|
def log(self, message):
|
||||||
fnum = self.tfs.openedFiles.index(self)
|
fnum = self.tfs.openedFiles.index(self)
|
||||||
logging.info("[Thread No.%d] %s\n" % (fnum, message))
|
logging.info("[Thread No.%d] %s\n" % (fnum, message))
|
||||||
|
|
||||||
def Pieces(self):
|
def Pieces(self):
|
||||||
startPiece, _ = self.pieceFromOffset(1)
|
startPiece, _ = self.pieceFromOffset(1)
|
||||||
endPiece, _ = self.pieceFromOffset(self.size - 1)
|
endPiece, _ = self.pieceFromOffset(self.size - 1)
|
||||||
return startPiece, endPiece
|
return startPiece, endPiece
|
||||||
|
|
||||||
def SetPriority(self, priority):
|
def SetPriority(self, priority):
|
||||||
self.tfs.setPriority(self.index, priority)
|
self.tfs.setPriority(self.index, priority)
|
||||||
|
|
||||||
def readOffset(self):
|
def readOffset(self):
|
||||||
if os.getenv('ANDROID_ROOT'):
|
if os.getenv('ANDROID_ROOT'):
|
||||||
return libc.lseek64(self.filePtr, 0, os.SEEK_CUR)
|
return libc.lseek64(self.filePtr, 0, os.SEEK_CUR)
|
||||||
else:
|
else:
|
||||||
return self.filePtr.seek(0, os.SEEK_CUR)
|
return self.filePtr.seek(0, os.SEEK_CUR)
|
||||||
|
|
||||||
def havePiece(self, piece):
|
def havePiece(self, piece):
|
||||||
return self.tfs.handle.have_piece(piece)
|
return self.tfs.handle.have_piece(piece)
|
||||||
|
|
||||||
def pieceFromOffset(self, offset):
|
def pieceFromOffset(self, offset):
|
||||||
piece = int((self.offset + offset) / self.piece_length)
|
piece = int((self.offset + offset) / self.piece_length)
|
||||||
pieceOffset = int((self.offset + offset) % self.piece_length)
|
pieceOffset = int((self.offset + offset) % self.piece_length)
|
||||||
return piece, pieceOffset
|
return piece, pieceOffset
|
||||||
|
|
||||||
def waitForPiece(self, piece):
|
def waitForPiece(self, piece):
|
||||||
def set_deadlines(p):
|
def set_deadlines(p):
|
||||||
next_piece = p + 1
|
next_piece = p + 1
|
||||||
BUF_SIZE = 2 # Лучшее враг хорошего
|
BUF_SIZE = 2 # Лучшее враг хорошего
|
||||||
for i in range(BUF_SIZE):
|
for i in range(BUF_SIZE):
|
||||||
if (next_piece + i < self.endPiece and
|
if (next_piece + i < self.endPiece and
|
||||||
not self.pieces_deadlined[(next_piece + i) - self.startPiece] and not self.havePiece(next_piece + i)):
|
not self.pieces_deadlined[(next_piece + i) - self.startPiece] and not self.havePiece(
|
||||||
|
next_piece + i)):
|
||||||
self.tfs.handle.set_piece_deadline(next_piece + i, 70 + (20 * i))
|
self.tfs.handle.set_piece_deadline(next_piece + i, 70 + (20 * i))
|
||||||
self.pieces_deadlined[(next_piece + i) - self.startPiece] = True
|
self.pieces_deadlined[(next_piece + i) - self.startPiece] = True
|
||||||
|
|
||||||
if not self.havePiece(piece):
|
if not self.havePiece(piece):
|
||||||
self.log('Waiting for piece %d' % (piece,))
|
self.log('Waiting for piece %d' % (piece,))
|
||||||
self.tfs.handle.set_piece_deadline(piece, 50)
|
self.tfs.handle.set_piece_deadline(piece, 50)
|
||||||
|
@ -217,9 +237,10 @@ class TorrentFile(object):
|
||||||
return False
|
return False
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
if not isinstance(self.pdl_thread, threading.Thread) or not self.pdl_thread.is_alive():
|
if not isinstance(self.pdl_thread, threading.Thread) or not self.pdl_thread.is_alive():
|
||||||
self.pdl_thread = threading.Thread(target = set_deadlines, args = (piece,))
|
self.pdl_thread = threading.Thread(target=set_deadlines, args=(piece,))
|
||||||
self.pdl_thread.start()
|
self.pdl_thread.start()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def Close(self):
|
def Close(self):
|
||||||
if self.closed: return
|
if self.closed: return
|
||||||
self.log('Closing %s...' % (self.name,))
|
self.log('Closing %s...' % (self.name,))
|
||||||
|
@ -231,6 +252,7 @@ class TorrentFile(object):
|
||||||
else:
|
else:
|
||||||
self.filePtr.close()
|
self.filePtr.close()
|
||||||
self.filePtr = None
|
self.filePtr = None
|
||||||
|
|
||||||
def ShowPieces(self):
|
def ShowPieces(self):
|
||||||
pieces = self.tfs.handle.status().pieces
|
pieces = self.tfs.handle.status().pieces
|
||||||
str_ = ''
|
str_ = ''
|
||||||
|
@ -240,6 +262,7 @@ class TorrentFile(object):
|
||||||
else:
|
else:
|
||||||
str_ += "#"
|
str_ += "#"
|
||||||
self.log(str_)
|
self.log(str_)
|
||||||
|
|
||||||
def Read(self, buf):
|
def Read(self, buf):
|
||||||
filePtr = self.__fileptr_()
|
filePtr = self.__fileptr_()
|
||||||
if filePtr is None:
|
if filePtr is None:
|
||||||
|
@ -258,6 +281,7 @@ class TorrentFile(object):
|
||||||
else:
|
else:
|
||||||
read = filePtr.readinto(buf)
|
read = filePtr.readinto(buf)
|
||||||
return read
|
return read
|
||||||
|
|
||||||
def Seek(self, offset, whence):
|
def Seek(self, offset, whence):
|
||||||
filePtr = self.__fileptr_()
|
filePtr = self.__fileptr_()
|
||||||
if filePtr is None: return
|
if filePtr is None: return
|
||||||
|
@ -270,9 +294,11 @@ class TorrentFile(object):
|
||||||
newOffset = filePtr.seek(offset, whence)
|
newOffset = filePtr.seek(offset, whence)
|
||||||
self.log('Seeking to %d/%d' % (newOffset, self.size))
|
self.log('Seeking to %d/%d' % (newOffset, self.size))
|
||||||
return newOffset
|
return newOffset
|
||||||
|
|
||||||
def IsComplete(self):
|
def IsComplete(self):
|
||||||
return self.downloaded == self.size
|
return self.downloaded == self.size
|
||||||
|
|
||||||
|
|
||||||
#######################################################################################
|
#######################################################################################
|
||||||
|
|
||||||
class TorrentFS(object):
|
class TorrentFS(object):
|
||||||
|
@ -296,8 +322,9 @@ class TorrentFS(object):
|
||||||
num_files = self.info.num_files()
|
num_files = self.info.num_files()
|
||||||
for i in range(num_files):
|
for i in range(num_files):
|
||||||
self.setPriority(i, 0)
|
self.setPriority(i, 0)
|
||||||
|
|
||||||
def file(self, index):
|
def file(self, index):
|
||||||
for name in self.files.keys():
|
for name in list(self.files.keys()):
|
||||||
if self.files[name].index == index:
|
if self.files[name].index == index:
|
||||||
return self.files[name]
|
return self.files[name]
|
||||||
file_ = self.__file_at_(index)
|
file_ = self.__file_at_(index)
|
||||||
|
@ -311,22 +338,27 @@ class TorrentFS(object):
|
||||||
logging.info('Closing %d opened file(s)' % (len(self.openedFiles),))
|
logging.info('Closing %d opened file(s)' % (len(self.openedFiles),))
|
||||||
for f in self.openedFiles:
|
for f in self.openedFiles:
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
def addOpenedFile(self, file_):
|
def addOpenedFile(self, file_):
|
||||||
self.openedFiles.append(file_)
|
self.openedFiles.append(file_)
|
||||||
|
|
||||||
def setPriority(self, index, priority):
|
def setPriority(self, index, priority):
|
||||||
if self.priorities[index] != priority:
|
if self.priorities[index] != priority:
|
||||||
logging.info('Setting %s priority to %d' % (self.info.file_at(index).path, priority))
|
logging.info('Setting %s priority to %d' % (self.info.file_at(index).path, priority))
|
||||||
self.priorities[index] = priority
|
self.priorities[index] = priority
|
||||||
self.handle.file_priority(index, priority)
|
self.handle.file_priority(index, priority)
|
||||||
|
|
||||||
def findOpenedFile(self, file):
|
def findOpenedFile(self, file):
|
||||||
for i, f in enumerate(self.openedFiles):
|
for i, f in enumerate(self.openedFiles):
|
||||||
if f == file:
|
if f == file:
|
||||||
return i
|
return i
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
def removeOpenedFile(self, file):
|
def removeOpenedFile(self, file):
|
||||||
pos = self.findOpenedFile(file)
|
pos = self.findOpenedFile(file)
|
||||||
if pos >= 0:
|
if pos >= 0:
|
||||||
del self.openedFiles[pos]
|
del self.openedFiles[pos]
|
||||||
|
|
||||||
def waitForMetadata(self):
|
def waitForMetadata(self):
|
||||||
if not self.handle.status().has_metadata:
|
if not self.handle.status().has_metadata:
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
@ -334,19 +366,24 @@ class TorrentFS(object):
|
||||||
self.info = self.handle.torrent_file()
|
self.info = self.handle.torrent_file()
|
||||||
except:
|
except:
|
||||||
self.info = self.handle.get_torrent_info()
|
self.info = self.handle.get_torrent_info()
|
||||||
|
|
||||||
def HasTorrentInfo(self):
|
def HasTorrentInfo(self):
|
||||||
return self.info is not None
|
return self.info is not None
|
||||||
|
|
||||||
def LoadFileProgress(self):
|
def LoadFileProgress(self):
|
||||||
self.progresses = self.handle.file_progress()
|
self.progresses = self.handle.file_progress()
|
||||||
for k in self.files.keys():
|
for k in list(self.files.keys()):
|
||||||
self.files[k].downloaded = self.getFileDownloadedBytes(self.files[k].index)
|
self.files[k].downloaded = self.getFileDownloadedBytes(self.files[k].index)
|
||||||
if self.files[k].size > 0: self.files[k].progress = float(self.files[k].downloaded) / float(self.files[k].size)
|
if self.files[k].size > 0: self.files[k].progress = float(self.files[k].downloaded) / float(
|
||||||
|
self.files[k].size)
|
||||||
|
|
||||||
def getFileDownloadedBytes(self, i):
|
def getFileDownloadedBytes(self, i):
|
||||||
try:
|
try:
|
||||||
bytes_ = self.progresses[i]
|
bytes_ = self.progresses[i]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
bytes_ = 0
|
bytes_ = 0
|
||||||
return bytes_
|
return bytes_
|
||||||
|
|
||||||
def __files_(self):
|
def __files_(self):
|
||||||
info = self.info
|
info = self.info
|
||||||
files_ = []
|
files_ = []
|
||||||
|
@ -354,9 +391,10 @@ class TorrentFS(object):
|
||||||
file_ = self.__file_at_(i)
|
file_ = self.__file_at_(i)
|
||||||
file_.downloaded = self.getFileDownloadedBytes(i)
|
file_.downloaded = self.getFileDownloadedBytes(i)
|
||||||
if file_.size > 0:
|
if file_.size > 0:
|
||||||
file_.progress = float(file_.downloaded)/float(file_.size)
|
file_.progress = float(file_.downloaded) / float(file_.size)
|
||||||
files_.append(file_)
|
files_.append(file_)
|
||||||
return files_
|
return files_
|
||||||
|
|
||||||
def __file_at_(self, index):
|
def __file_at_(self, index):
|
||||||
info = self.info
|
info = self.info
|
||||||
fileEntry = info.file_at(index)
|
fileEntry = info.file_at(index)
|
||||||
|
@ -368,15 +406,18 @@ class TorrentFS(object):
|
||||||
path,
|
path,
|
||||||
index
|
index
|
||||||
)
|
)
|
||||||
|
|
||||||
def FileByName(self, name):
|
def FileByName(self, name):
|
||||||
for i, f in enumerate(self.info.files()):
|
for i, f in enumerate(self.info.files()):
|
||||||
if f.path == name:
|
if f.path == name:
|
||||||
return self.__file_at_(i)
|
return self.__file_at_(i)
|
||||||
raise IOError
|
raise IOError
|
||||||
|
|
||||||
def Open(self, name):
|
def Open(self, name):
|
||||||
if self.shuttingDown or not self.HasTorrentInfo():
|
if self.shuttingDown or not self.HasTorrentInfo():
|
||||||
raise IOError
|
raise IOError
|
||||||
return self.OpenFile(name)
|
return self.OpenFile(name)
|
||||||
|
|
||||||
def checkPriorities(self):
|
def checkPriorities(self):
|
||||||
for index, priority in enumerate(self.priorities):
|
for index, priority in enumerate(self.priorities):
|
||||||
if priority == 0:
|
if priority == 0:
|
||||||
|
@ -388,6 +429,7 @@ class TorrentFS(object):
|
||||||
break
|
break
|
||||||
if not found:
|
if not found:
|
||||||
self.setPriority(index, 0)
|
self.setPriority(index, 0)
|
||||||
|
|
||||||
def OpenFile(self, name):
|
def OpenFile(self, name):
|
||||||
try:
|
try:
|
||||||
tf = self.FileByName(name)
|
tf = self.FileByName(name)
|
||||||
|
@ -405,24 +447,27 @@ class TorrentFS(object):
|
||||||
self.checkPriorities()
|
self.checkPriorities()
|
||||||
return tf
|
return tf
|
||||||
|
|
||||||
|
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
||||||
def handle_error(self, *args, **kwargs):
|
def handle_error(self, *args, **kwargs):
|
||||||
'''Обходим злосчастный "Broken Pipe" и прочие трейсы'''
|
'''Обходим злосчастный "Broken Pipe" и прочие трейсы'''
|
||||||
if not AVOID_HTTP_SERVER_EXCEPTION_OUTPUT:
|
if not AVOID_HTTP_SERVER_EXCEPTION_OUTPUT:
|
||||||
BaseHTTPServer.HTTPServer.handle_error(self, *args, **kwargs)
|
http.server.HTTPServer.handle_error(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def HttpHandlerFactory():
|
def HttpHandlerFactory():
|
||||||
class HttpHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
class HttpHandler(http.server.BaseHTTPRequestHandler):
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
#print ('---Headers---\n%s\n' % (self.headers,))
|
# print ('---Headers---\n%s\n' % (self.headers,))
|
||||||
#print ('---Request---\n%s\n' % (self.path,))
|
# print ('---Request---\n%s\n' % (self.path,))
|
||||||
if self.path.startswith('/files/'):
|
if self.path.startswith('/files/'):
|
||||||
self.filesHandler()
|
self.filesHandler()
|
||||||
else:
|
else:
|
||||||
self.send_error(404, 'Not found')
|
self.send_error(404, 'Not found')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
|
|
||||||
def filesHandler(self):
|
def filesHandler(self):
|
||||||
f, start_range, end_range = self.send_head()
|
f, start_range, end_range = self.send_head()
|
||||||
if not f.closed:
|
if not f.closed:
|
||||||
|
@ -454,15 +499,16 @@ def HttpHandlerFactory():
|
||||||
total += chunk
|
total += chunk
|
||||||
start_range += chunk
|
start_range += chunk
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
def send_head(self):
|
def send_head(self):
|
||||||
fname = urllib.unquote(self.path.lstrip('/files/'))
|
fname = urllib.parse.unquote(self.path.lstrip('/files/'))
|
||||||
try:
|
try:
|
||||||
f = self.server.root_obj.TorrentFS.Open(fname)
|
f = self.server.root_obj.TorrentFS.Open(fname)
|
||||||
except IOError:
|
except IOError:
|
||||||
self.send_error(404, "File not found")
|
self.send_error(404, "File not found")
|
||||||
return (None, 0, 0)
|
return (None, 0, 0)
|
||||||
_, ext = os.path.splitext(fname)
|
_, ext = os.path.splitext(fname)
|
||||||
ctype = (ext != '' and ext in VIDEO_EXTS.keys())and VIDEO_EXTS[ext] or 'application/octet-stream'
|
ctype = (ext != '' and ext in list(VIDEO_EXTS.keys())) and VIDEO_EXTS[ext] or 'application/octet-stream'
|
||||||
if "Range" in self.headers:
|
if "Range" in self.headers:
|
||||||
self.send_response(206, 'Partial Content')
|
self.send_response(206, 'Partial Content')
|
||||||
else:
|
else:
|
||||||
|
@ -489,28 +535,32 @@ def HttpHandlerFactory():
|
||||||
self.send_header("Content-Length", end_range - start_range)
|
self.send_header("Content-Length", end_range - start_range)
|
||||||
self.send_header("Last-Modified", self.date_time_string(f.fileEntry.mtime))
|
self.send_header("Last-Modified", self.date_time_string(f.fileEntry.mtime))
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
#print "Sending Bytes ",start_range, " to ", end_range, "...\n"
|
# print "Sending Bytes ",start_range, " to ", end_range, "...\n"
|
||||||
return (f, start_range, end_range)
|
return f, start_range, end_range
|
||||||
|
|
||||||
# Вырубаем access-log
|
# Вырубаем access-log
|
||||||
def log_message(self, format, *args):
|
def log_message(self, fmt, *args):
|
||||||
return
|
return
|
||||||
|
|
||||||
return HttpHandler
|
return HttpHandler
|
||||||
|
|
||||||
|
|
||||||
class Pyrrent2http(object):
|
class Pyrrent2http(object):
|
||||||
pause = False
|
pause = False
|
||||||
def __init__(self, uri = '', bindAddress = 'localhost:5001', downloadPath = '.',
|
|
||||||
idleTimeout = -1, keepComplete = False,
|
def __init__(self, uri='', bindAddress='localhost:5001', downloadPath='.',
|
||||||
keepIncomplete = False, keepFiles = False, showAllStats = False,
|
idleTimeout=-1, keepComplete=False,
|
||||||
showOverallProgress = False, showFilesProgress = False,
|
keepIncomplete=False, keepFiles=False, showAllStats=False,
|
||||||
showPiecesProgress = False, debugAlerts = False,
|
showOverallProgress=False, showFilesProgress=False,
|
||||||
exitOnFinish = False, resumeFile = '', stateFile = '',
|
showPiecesProgress=False, debugAlerts=False,
|
||||||
userAgent = USER_AGENT, dhtRouters = '', trackers = '',
|
exitOnFinish=False, resumeFile='', stateFile='',
|
||||||
listenPort = 6881, torrentConnectBoost = 50, connectionSpeed = 50,
|
userAgent=USER_AGENT, dhtRouters='', trackers='',
|
||||||
peerConnectTimeout = 15, requestTimeout = 20, maxDownloadRate = -1,
|
listenPort=6881, torrentConnectBoost=50, connectionSpeed=50,
|
||||||
maxUploadRate = -1, connectionsLimit = 200, encryption = 1,
|
peerConnectTimeout=15, requestTimeout=20, maxDownloadRate=-1,
|
||||||
minReconnectTime = 60, maxFailCount = 3, noSparseFile = False,
|
maxUploadRate=-1, connectionsLimit=200, encryption=1,
|
||||||
randomPort = False, enableScrape = False, enableDHT = True,
|
minReconnectTime=60, maxFailCount=3, noSparseFile=False,
|
||||||
enableLSD = True, enableUPNP = True, enableNATPMP = True, enableUTP = True, enableTCP = True, proxy=None):
|
randomPort=False, enableScrape=False, enableDHT=True,
|
||||||
|
enableLSD=True, enableUPNP=True, enableNATPMP=True, enableUTP=True, enableTCP=True, proxy=None):
|
||||||
self.torrentHandle = None
|
self.torrentHandle = None
|
||||||
self.forceShutdown = False
|
self.forceShutdown = False
|
||||||
self.session = None
|
self.session = None
|
||||||
|
@ -608,7 +658,7 @@ class Pyrrent2http(object):
|
||||||
startTier = 256 - len(trackers)
|
startTier = 256 - len(trackers)
|
||||||
for n in range(len(trackers)):
|
for n in range(len(trackers)):
|
||||||
tracker = trackers[n].strip()
|
tracker = trackers[n].strip()
|
||||||
logging.info('Adding tracker: %s' % (tracker,) )
|
logging.info('Adding tracker: %s' % (tracker,))
|
||||||
self.torrentHandle.add_tracker({'url': tracker})
|
self.torrentHandle.add_tracker({'url': tracker})
|
||||||
if self.config.enableScrape:
|
if self.config.enableScrape:
|
||||||
logging.info('Sending scrape request to tracker')
|
logging.info('Sending scrape request to tracker')
|
||||||
|
@ -623,7 +673,7 @@ class Pyrrent2http(object):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(e.args)
|
logging.error(e.args)
|
||||||
name = self.TorrentFS.info.name()
|
name = self.TorrentFS.info.name()
|
||||||
self.torrent_name = name.decode(chardet.detect(name)['encoding'])
|
self.torrent_name = name
|
||||||
|
|
||||||
def startHTTP(self):
|
def startHTTP(self):
|
||||||
logging.info('Starting HTTP Server...')
|
logging.info('Starting HTTP Server...')
|
||||||
|
@ -635,7 +685,7 @@ class Pyrrent2http(object):
|
||||||
srv_port = int(strport)
|
srv_port = int(strport)
|
||||||
self.httpListener = ThreadingHTTPServer((host, srv_port), handler)
|
self.httpListener = ThreadingHTTPServer((host, srv_port), handler)
|
||||||
self.httpListener.root_obj = self
|
self.httpListener.root_obj = self
|
||||||
self.listener_thread = threading.Thread(target = self.httpListener.serve_forever)
|
self.listener_thread = threading.Thread(target=self.httpListener.serve_forever)
|
||||||
self.listener_thread.start()
|
self.listener_thread.start()
|
||||||
|
|
||||||
def startServices(self):
|
def startServices(self):
|
||||||
|
@ -681,12 +731,12 @@ class Pyrrent2http(object):
|
||||||
### Непонятно, как заставить использовать прокси только для подключения к трекеру?
|
### Непонятно, как заставить использовать прокси только для подключения к трекеру?
|
||||||
if self.config.proxy is not None:
|
if self.config.proxy is not None:
|
||||||
ps = lt.proxy_settings()
|
ps = lt.proxy_settings()
|
||||||
#peer_ps = lt.proxy_settings()
|
# peer_ps = lt.proxy_settings()
|
||||||
#peer_ps.type = lt.proxy_type.none
|
# peer_ps.type = lt.proxy_type.none
|
||||||
ps.hostname = self.config.proxy['host']
|
ps.hostname = self.config.proxy['host']
|
||||||
ps.port = self.config.proxy['port']
|
ps.port = self.config.proxy['port']
|
||||||
ps.type = lt.proxy_type.socks5
|
ps.type = lt.proxy_type.socks5
|
||||||
#self.session.set_peer_proxy(peer_ps)
|
# self.session.set_peer_proxy(peer_ps)
|
||||||
self.session.set_proxy(ps)
|
self.session.set_proxy(ps)
|
||||||
settings['force_proxy'] = False
|
settings['force_proxy'] = False
|
||||||
settings['proxy_peer_connections'] = False
|
settings['proxy_peer_connections'] = False
|
||||||
|
@ -763,28 +813,29 @@ class Pyrrent2http(object):
|
||||||
tstatus = self.torrentHandle.status()
|
tstatus = self.torrentHandle.status()
|
||||||
|
|
||||||
status = {
|
status = {
|
||||||
'name' : self.torrent_name,
|
'name': self.torrent_name,
|
||||||
'state' : int(tstatus.state),
|
'state': int(tstatus.state),
|
||||||
'state_str' : str(tstatus.state),
|
'state_str': str(tstatus.state),
|
||||||
'error' : tstatus.error,
|
'error': tstatus.error,
|
||||||
'progress' : tstatus.progress,
|
'progress': tstatus.progress,
|
||||||
'download_rate' : tstatus.download_rate / 1024,
|
'download_rate': tstatus.download_rate // 1024,
|
||||||
'upload_rate' : tstatus.upload_rate / 1024,
|
'upload_rate': tstatus.upload_rate // 1024,
|
||||||
'total_download' : tstatus.total_download,
|
'total_download': tstatus.total_download,
|
||||||
'total_upload' : tstatus.total_upload,
|
'total_upload': tstatus.total_upload,
|
||||||
'num_peers' : tstatus.num_peers,
|
'num_peers': tstatus.num_peers,
|
||||||
'num_seeds' : tstatus.num_seeds,
|
'num_seeds': tstatus.num_seeds,
|
||||||
'total_seeds' : tstatus.num_complete,
|
'total_seeds': tstatus.num_complete,
|
||||||
'total_peers' : tstatus.num_incomplete
|
'total_peers': tstatus.num_incomplete
|
||||||
}
|
}
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def Ls(self, index):
|
def Ls(self, index):
|
||||||
fi = {}
|
fi = {}
|
||||||
if self.TorrentFS.HasTorrentInfo():
|
if self.TorrentFS.HasTorrentInfo():
|
||||||
x = [n for n in self.TorrentFS.files.keys() if self.TorrentFS.files[n].index == index]
|
x = [n for n in list(self.TorrentFS.files.keys()) if self.TorrentFS.files[n].index == index]
|
||||||
name = x[0]
|
name = x[0]
|
||||||
files = self.TorrentFS.files
|
files = self.TorrentFS.files
|
||||||
Url = 'http://' + self.config.bindAddress + '/files/' + urllib.quote(name)
|
Url = 'http://' + self.config.bindAddress + '/files/' + urllib.parse.quote(name)
|
||||||
fi = {
|
fi = {
|
||||||
'index': files[name].index,
|
'index': files[name].index,
|
||||||
'name': files[name].unicode_name,
|
'name': files[name].unicode_name,
|
||||||
|
@ -797,6 +848,7 @@ class Pyrrent2http(object):
|
||||||
'url': Url
|
'url': Url
|
||||||
}
|
}
|
||||||
return fi
|
return fi
|
||||||
|
|
||||||
def Peers(self):
|
def Peers(self):
|
||||||
peers = {'peers': []}
|
peers = {'peers': []}
|
||||||
for peer in self.torrentHandle.get_peer_info():
|
for peer in self.torrentHandle.get_peer_info():
|
||||||
|
@ -806,8 +858,8 @@ class Pyrrent2http(object):
|
||||||
'Ip': peer.ip,
|
'Ip': peer.ip,
|
||||||
'Flags': peer.flags,
|
'Flags': peer.flags,
|
||||||
'Source': peer.source,
|
'Source': peer.source,
|
||||||
'UpSpeed': peer.up_speed/1024,
|
'UpSpeed': peer.up_speed // 1024,
|
||||||
'DownSpeed': peer.down_speed/1024,
|
'DownSpeed': peer.down_speed // 1024,
|
||||||
'TotalDownload': peer.total_download,
|
'TotalDownload': peer.total_download,
|
||||||
'TotalUpload': peer.total_upload,
|
'TotalUpload': peer.total_upload,
|
||||||
'Country': peer.country,
|
'Country': peer.country,
|
||||||
|
@ -815,12 +867,14 @@ class Pyrrent2http(object):
|
||||||
}
|
}
|
||||||
peers['peers'].append(pi)
|
peers['peers'].append(pi)
|
||||||
return peers
|
return peers
|
||||||
|
|
||||||
def consumeAlerts(self):
|
def consumeAlerts(self):
|
||||||
alerts = self.session.pop_alerts()
|
alerts = self.session.pop_alerts()
|
||||||
for alert in alerts:
|
for alert in alerts:
|
||||||
if type(alert) == lt.save_resume_data_alert:
|
if type(alert) == lt.save_resume_data_alert:
|
||||||
self.processSaveResumeDataAlert(alert)
|
self.processSaveResumeDataAlert(alert)
|
||||||
break
|
break
|
||||||
|
|
||||||
def waitForAlert(self, alert_type, timeout):
|
def waitForAlert(self, alert_type, timeout):
|
||||||
start = time.time()
|
start = time.time()
|
||||||
while True:
|
while True:
|
||||||
|
@ -831,6 +885,7 @@ class Pyrrent2http(object):
|
||||||
alert = self.session.pop_alert()
|
alert = self.session.pop_alert()
|
||||||
if type(alert) == alert_type:
|
if type(alert) == alert_type:
|
||||||
return alert
|
return alert
|
||||||
|
|
||||||
def loop(self):
|
def loop(self):
|
||||||
self.saveResumeDataTicker = Ticker(5)
|
self.saveResumeDataTicker = Ticker(5)
|
||||||
time_start = time.time()
|
time_start = time.time()
|
||||||
|
@ -859,16 +914,18 @@ class Pyrrent2http(object):
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
strerror = e.args
|
strerror = e.args
|
||||||
logging.error(strerror)
|
logging.error(strerror)
|
||||||
def saveResumeData(self, async = False):
|
|
||||||
|
def saveResumeData(self, async_=False):
|
||||||
if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '':
|
if not self.torrentHandle.status().need_save_resume or self.config.resumeFile == '':
|
||||||
return False
|
return False
|
||||||
self.torrentHandle.save_resume_data(lt.save_resume_flags_t.flush_disk_cache)
|
self.torrentHandle.save_resume_data(lt.save_resume_flags_t.flush_disk_cache)
|
||||||
if not async:
|
if not async_:
|
||||||
alert = self.waitForAlert(lt.save_resume_data_alert, 5)
|
alert = self.waitForAlert(lt.save_resume_data_alert, 5)
|
||||||
if alert == None:
|
if alert is None:
|
||||||
return False
|
return False
|
||||||
self.processSaveResumeDataAlert(alert)
|
self.processSaveResumeDataAlert(alert)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def saveSessionState(self):
|
def saveSessionState(self):
|
||||||
if self.config.stateFile == '':
|
if self.config.stateFile == '':
|
||||||
return
|
return
|
||||||
|
@ -882,6 +939,7 @@ class Pyrrent2http(object):
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
strerror = e.args
|
strerror = e.args
|
||||||
logging.error(strerror)
|
logging.error(strerror)
|
||||||
|
|
||||||
def removeFiles(self, files):
|
def removeFiles(self, files):
|
||||||
for file in files:
|
for file in files:
|
||||||
try:
|
try:
|
||||||
|
@ -897,6 +955,7 @@ class Pyrrent2http(object):
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
path_ = os.path.dirname(path)
|
path_ = os.path.dirname(path)
|
||||||
path = path_[-1] == os.path.sep and path_[:-1] or path_
|
path = path_[-1] == os.path.sep and path_[:-1] or path_
|
||||||
|
|
||||||
def filesToRemove(self):
|
def filesToRemove(self):
|
||||||
files = []
|
files = []
|
||||||
if self.TorrentFS.HasTorrentInfo():
|
if self.TorrentFS.HasTorrentInfo():
|
||||||
|
@ -907,6 +966,7 @@ class Pyrrent2http(object):
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
files.append(path)
|
files.append(path)
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def removeTorrent(self):
|
def removeTorrent(self):
|
||||||
files = []
|
files = []
|
||||||
flag = 0
|
flag = 0
|
||||||
|
@ -922,6 +982,7 @@ class Pyrrent2http(object):
|
||||||
logging.info('Waiting for files to be removed')
|
logging.info('Waiting for files to be removed')
|
||||||
self.waitForAlert(lt.torrent_deleted_alert, 15)
|
self.waitForAlert(lt.torrent_deleted_alert, 15)
|
||||||
self.removeFiles(files)
|
self.removeFiles(files)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
logging.info('Stopping pyrrent2http...')
|
logging.info('Stopping pyrrent2http...')
|
||||||
self.forceShutdown = True
|
self.forceShutdown = True
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
class State:
|
||||||
|
QUEUED_FOR_CHECKING = 0
|
||||||
|
CHECKING_FILES = 1
|
||||||
|
DOWNLOADING_METADATA = 2
|
||||||
|
DOWNLOADING = 3
|
||||||
|
FINISHED = 4
|
||||||
|
SEEDING = 5
|
||||||
|
ALLOCATING = 6
|
||||||
|
CHECKING_RESUME_DATA = 7
|
||||||
|
|
||||||
|
|
||||||
|
class MediaType:
|
||||||
|
UNKNOWN = None
|
||||||
|
AUDIO = 'audio'
|
||||||
|
VIDEO = 'video'
|
||||||
|
SUBTITLES = 'subtitles'
|
||||||
|
|
||||||
|
|
||||||
|
class Encryption:
|
||||||
|
FORCED = 0
|
||||||
|
ENABLED = 1
|
||||||
|
DISABLED = 2
|
|
@ -1,31 +1,41 @@
|
||||||
import sys
|
|
||||||
import socket
|
|
||||||
import chardet
|
|
||||||
import os
|
|
||||||
from . import MediaType
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import urlparse, urllib
|
import os
|
||||||
|
import socket
|
||||||
|
import urllib.error
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.parse
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
import chardet
|
||||||
|
import sys
|
||||||
|
import xbmc
|
||||||
|
|
||||||
|
from .structs import MediaType
|
||||||
|
|
||||||
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']
|
||||||
|
|
||||||
|
|
||||||
class Struct(dict):
|
class Struct(dict):
|
||||||
def __getattr__(self, attr):
|
def __getattr__(self, attr):
|
||||||
return self[attr]
|
return self[attr]
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
def __setattr__(self, attr, value):
|
||||||
self[attr] = value
|
self[attr] = value
|
||||||
|
|
||||||
|
|
||||||
def uri2path(uri):
|
def uri2path(uri):
|
||||||
if uri[1] == ':' and sys.platform.startswith('win'):
|
if uri[1] == ':' and sys.platform.startswith('win'):
|
||||||
uri = 'file:///' + uri
|
uri = 'file:///' + uri
|
||||||
fileUri = urlparse.urlparse(uri)
|
fileUri = urllib.parse.urlparse(uri)
|
||||||
if fileUri.scheme == 'file':
|
if fileUri.scheme == 'file':
|
||||||
uriPath = fileUri.path
|
uriPath = fileUri.path
|
||||||
if uriPath != '' and sys.platform.startswith('win') and (os.path.sep == uriPath[0] or uriPath[0] == '/'):
|
if uriPath != '' and sys.platform.startswith('win') and (os.path.sep == uriPath[0] or uriPath[0] == '/'):
|
||||||
uriPath = uriPath[1:]
|
uriPath = uriPath[1:]
|
||||||
absPath = os.path.abspath(urllib.unquote(uriPath))
|
absPath = os.path.abspath(urllib.parse.unquote(uriPath))
|
||||||
return localize_path(absPath)
|
return localize_path(absPath)
|
||||||
|
|
||||||
|
|
||||||
def detect_media_type(name):
|
def detect_media_type(name):
|
||||||
ext = os.path.splitext(name)[1]
|
ext = os.path.splitext(name)[1]
|
||||||
if ext in SUBTITLES_FORMATS:
|
if ext in SUBTITLES_FORMATS:
|
||||||
|
@ -41,24 +51,31 @@ def detect_media_type(name):
|
||||||
return MediaType.VIDEO
|
return MediaType.VIDEO
|
||||||
else:
|
else:
|
||||||
return MediaType.UNKNOWN
|
return MediaType.UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
def unicode_msg(tmpl, args):
|
def unicode_msg(tmpl, args):
|
||||||
msg = isinstance(tmpl, unicode) and tmpl or tmpl.decode(chardet.detect(tmpl)['encoding'])
|
msg = isinstance(tmpl, str) and tmpl or tmpl.decode(chardet.detect(tmpl)['encoding'])
|
||||||
arg_ = []
|
arg_ = []
|
||||||
for a in args:
|
for a in args:
|
||||||
arg_.append(isinstance(a, unicode) and a or a.decode(chardet.detect(a)['encoding']))
|
arg_.append(isinstance(a, str) and a or a.decode(chardet.detect(a)['encoding']))
|
||||||
return msg % tuple(arg_)
|
return msg % tuple(arg_)
|
||||||
|
|
||||||
|
|
||||||
def encode_msg(msg):
|
def encode_msg(msg):
|
||||||
msg = isinstance(msg, unicode) and msg.encode(sys.getfilesystemencoding() != 'ascii' and sys.getfilesystemencoding() or 'utf-8') or msg
|
msg = isinstance(msg, str) and msg.encode(
|
||||||
|
sys.getfilesystemencoding() != 'ascii' and sys.getfilesystemencoding() or 'utf-8') or msg
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def localize_path(path):
|
def localize_path(path):
|
||||||
if not isinstance(path, unicode): path = path.decode(chardet.detect(path)['encoding'])
|
if isinstance(path, bytes):
|
||||||
if not sys.platform.startswith('win'):
|
path = path.decode(chardet.detect(path)['encoding'])
|
||||||
path = path.encode((sys.getfilesystemencoding() not in ('ascii', 'ANSI_X3.4-1968')) and sys.getfilesystemencoding() or 'utf-8')
|
# if not sys.platform.startswith('win'):
|
||||||
|
# path = path.encode(
|
||||||
|
# (sys.getfilesystemencoding() not in ('ascii', 'ANSI_X3.4-1968')) and sys.getfilesystemencoding() or 'utf-8')
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
def can_bind(host, port):
|
def can_bind(host, port):
|
||||||
"""
|
"""
|
||||||
Checks we can bind to specified host and port
|
Checks we can bind to specified host and port
|
||||||
|
@ -94,6 +111,6 @@ def find_free_port(host):
|
||||||
|
|
||||||
|
|
||||||
def ensure_fs_encoding(string):
|
def ensure_fs_encoding(string):
|
||||||
if isinstance(string, str):
|
if isinstance(string, bytes):
|
||||||
string = string.decode('utf-8')
|
string = string.decode('utf-8')
|
||||||
return string.encode(sys.getfilesystemencoding() or 'utf-8')
|
return string.encode(sys.getfilesystemencoding() or 'utf-8')
|
||||||
|
|
Loading…
Reference in New Issue