Compare commits

..

22 Commits

Author SHA1 Message Date
Роман Бородин 5a640c4ae4 зависимость 2022-03-14 11:57:48 +03:00
Роман Бородин 6e04f3fc14 Версия для Kodi 19 2022-03-14 09:10:42 +03:00
Роман Бородин f39749807f SOCKS-прокси для подключения к трекерам. Исправлена функция add_tracker 2020-04-28 22:25:14 +03:00
Бородин Роман 037e32ae9b - версия 0.9.9 2019-05-05 09:57:03 +03:00
Бородин Роман 228714810a принудительное завершение всего 2019-05-05 09:56:12 +03:00
inpos 0e81ccf6bc Таймер неверно опрашивался 2017-03-02 14:22:57 +03:00
Роман 04e63d29a0 Update README.md 2016-11-17 13:50:57 +04:00
Роман d3baa70ceb Fixed exception occurred when filenames contain russian letters 2016-11-17 13:49:54 +04:00
Роман 1733e20b0e Merge pull request #1 from kolosovski/cyrillic_filenames
Fixed exception occurred when filenames contain russian letters
2016-11-17 13:40:31 +04:00
Sergey Kolosovskiy 7683867a4e Fixed exceprion occurred when filenames contain russian letters 2016-11-16 20:01:18 +02:00
inpos 824daed99f Update README.md 2016-05-15 12:24:40 +04:00
inpos 32777fa959 мусор 2016-04-02 13:16:50 +03:00
inpos c594801130 0.9.6 - Android 2016-04-02 13:15:55 +03:00
inpos 7557ed8223 0.9.5 2016-03-29 17:59:44 +03:00
inpos a65987feea SEEK_CUR 2016-03-29 17:57:14 +03:00
inpos e23e447a1b Обработка паузы 2016-03-27 15:27:14 +03:00
inpos 0f5c8de27b кодировки на андроиде 2016-03-21 07:48:25 +03:00
inpos 0e541e9a68 ускорение при повторном использовании 2016-03-20 14:10:12 +03:00
inpos 2f4b1c9577 0.9.1 2016-03-19 23:03:48 +03:00
inpos efbfd8beff ошибочка 2016-03-19 22:14:32 +03:00
inpos 3ecaa41dbb Имя в статусе серии 2016-03-19 22:00:16 +03:00
inpos ad26862286 уточнение в журнале 2016-03-19 18:33:30 +03:00
9 changed files with 409 additions and 912 deletions

View File

@ -4,3 +4,6 @@ script.module.pyrrent2http
This add-on is engine for [plugin.video.torrenter](https://github.com/DiMartinoXBMC/plugin.video.torrenter) This add-on is engine for [plugin.video.torrenter](https://github.com/DiMartinoXBMC/plugin.video.torrenter)
This add-on can be used to stream media files from torrents without need to download entire files. This add-on can be used to stream media files from torrents without need to download entire files.
Данный движок для модуля Torrenter v2 медиа-центра KODI (XBMC) появился из-за отсутствия рабочего решения для Raspberry Pi.
Сейчас pyrrent2http работает на множестве платформ.

View File

@ -1,8 +1,8 @@
<?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="0.9.0" provider-name="inpos"> <addon id="script.module.pyrrent2http" name="pyrrent2http" version="1.1.1" 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" version="1.2.0"/>
<import addon="script.module.chardet" /> <import addon="script.module.chardet" />
</requires> </requires>
<extension point="xbmc.python.module" library="lib"/> <extension point="xbmc.python.module" library="lib"/>
@ -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>

View File

@ -1,3 +1,7 @@
[B]Version 1.0.0[/B]
+ Исправлен механизм добавления трекеров
+ Добавлена поддержка прокси SOCKS длч подключения к трекерам
[B]Version 0.9.0[/B] [B]Version 0.9.0[/B]
+ Изменён принцип работы с торрентом + Изменён принцип работы с торрентом

View File

@ -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

View File

@ -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,
@ -33,7 +39,7 @@ class Engine:
user_agent=None, startup_timeout=5, state_file='', enable_utp=True, enable_tcp=True, user_agent=None, startup_timeout=5, state_file='', enable_utp=True, enable_tcp=True,
debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50, debug_alerts=False, logger=None, torrent_connect_boost=50, connection_speed=50,
peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3, peer_connect_timeout=15, request_timeout=20, min_reconnect_time=60, max_failcount=3,
dht_routers=None, trackers=None): dht_routers=None, trackers=None, proxy=None):
""" """
Creates engine instance. It doesn't do anything except initializing object members. For starting engine use Creates engine instance. It doesn't do anything except initializing object members. For starting engine use
start() method. start() method.
@ -126,6 +132,7 @@ class Engine:
self.logger = logger self.logger = logger
self.uri = uri self.uri = uri
self.started = False self.started = False
self.proxy = proxy
@staticmethod @staticmethod
def _validate_save_path(path): def _validate_save_path(path):
@ -166,6 +173,7 @@ class Engine:
kwargs = { kwargs = {
'torrentConnectBoost': self.torrent_connect_boost, 'torrentConnectBoost': self.torrent_connect_boost,
'trackers': ",".join(self.trackers), 'trackers': ",".join(self.trackers),
'proxy': self.proxy,
'resumeFile': self.resume_file, 'resumeFile': self.resume_file,
'minReconnectTime': self.min_reconnect_time, 'minReconnectTime': self.min_reconnect_time,
'enableUPNP': self.enable_upnp, 'enableUPNP': self.enable_upnp,
@ -196,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,
@ -206,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)
@ -222,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
@ -234,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:
@ -245,6 +256,15 @@ class Engine:
raise Error("Can't start pyrrent2http, time is out", Error.TIMEOUT) raise Error("Can't start pyrrent2http, time is out", Error.TIMEOUT)
self._log("pyrrent2http successfully started.") self._log("pyrrent2http successfully started.")
def activate_file(self, index):
self.pyrrent2http.TorrentFS.file(index)
def pause(self):
self.pyrrent2http.pause = True
def resume(self):
self.pyrrent2http.pause = False
def check_torrent_error(self, status=None): def check_torrent_error(self, status=None):
""" """
It is recommended to call this method periodically to check if any libtorrent errors occurred. It is recommended to call this method periodically to check if any libtorrent errors occurred.
@ -269,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).
@ -286,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))
@ -296,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):
@ -324,13 +343,12 @@ class Engine:
:return: File with specified index :return: File with specified index
:rtype: FileStatus :rtype: FileStatus
""" """
files = self.pyrrent2http.Ls()['files'] filestatus = self.pyrrent2http.Ls(file_index)
if files: try:
for f in files: return FileStatus(**filestatus)
if f['index'] == file_index: except:
return FileStatus(**f)
raise Error("Requested file index (%d) is invalid" % (file_index,), Error.INVALID_FILE_INDEX, raise Error("Requested file index (%d) is invalid" % (file_index,), Error.INVALID_FILE_INDEX,
file_index=file_index) file_index=file_index)
def peers(self, timeout=10): def peers(self, timeout=10):
""" """
@ -346,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

View File

@ -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

462
lib/pyrrent2http/pyrrent2http.py 100644 → 100755
View File

@ -1,16 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
import chardet import chardet
try: try:
from python_libtorrent import get_libtorrent 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 import libtorrent as lt # @UnresolvedImport
except Exception as e: except Exception as e:
strerror = e.args strerror = e.args
print(strerror) print(strerror)
@ -18,28 +18,39 @@ 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'):
from ctypes import *
libc = CDLL('/system/lib/libc.so')
libc.lseek64.restype = c_ulonglong
libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint]
libc.read.restype = c_long
libc.read.argtypes = [c_uint, c_void_p, c_long]
O_RDONLY = 0
O_LARGEFILE = 0x8000
###################################################################################### ######################################################################################
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 CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @UndefinedVariable
MAX_PATH = 260 MAX_PATH = 260
_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),
@ -55,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.
@ -83,28 +96,32 @@ 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):
def __init__(self, interval): def __init__(self, interval):
self.tick = False self.tick = False
self._timer = None self._timer = None
self.interval = interval self.interval = interval
self.is_running = False self.is_running = False
self.start() self.start()
@property
def true(self): def true(self):
if self.tick: if self.tick:
self.tick = False self.tick = False
@ -127,23 +144,25 @@ class Ticker(object):
self._timer.cancel() self._timer.cancel()
self.is_running = False self.is_running = False
####################################################################################### #######################################################################################
class TorrentFile(object): class TorrentFile(object):
tfs = None tfs = None
closed = True closed = True
save_path = str() save_path = str()
fileEntry = None fileEntry = None
index = 0 index = 0
filePtr = None filePtr = None
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
@ -155,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
@ -165,34 +186,49 @@ class TorrentFile(object):
logging.info('Waiting for file: %s' % (self.save_path,)) logging.info('Waiting for file: %s' % (self.save_path,))
self.tfs.handle.flush_cache() self.tfs.handle.flush_cache()
time.sleep(0.5) time.sleep(0.5)
self.filePtr = io.open(self.save_path, 'rb') if os.getenv('ANDROID_ROOT'):
self.filePtr = libc.open(self.save_path, O_RDONLY | O_LARGEFILE, 755)
else:
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("[%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):
return self.filePtr.seek(0, io.SEEK_CUR) if os.getenv('ANDROID_ROOT'):
return libc.lseek64(self.filePtr, 0, os.SEEK_CUR)
else:
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)
@ -201,17 +237,22 @@ 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,))
self.tfs.removeOpenedFile(self) self.tfs.removeOpenedFile(self)
self.closed = True self.closed = True
if self.filePtr is not None: if self.filePtr is not None:
self.filePtr.close() if os.getenv('ANDROID_ROOT'):
libc.close(self.filePtr)
else:
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_ = ''
@ -221,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:
@ -231,55 +273,64 @@ class TorrentFile(object):
readOffset = self.readOffset() readOffset = self.readOffset()
startPiece, _ = self.pieceFromOffset(readOffset) startPiece, _ = self.pieceFromOffset(readOffset)
endPiece, _ = self.pieceFromOffset(readOffset + toRead) endPiece, _ = self.pieceFromOffset(readOffset + toRead)
for i in range(startPiece, endPiece + 1): for i in range(startPiece, endPiece + 1):
if not self.waitForPiece(i): if not self.waitForPiece(i):
raise IOError raise IOError
read = filePtr.readinto(buf) if os.getenv('ANDROID_ROOT'):
read = libc.read(self.filePtr, addressof(buf), toRead)
else:
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
if whence == os.SEEK_END: if whence == os.SEEK_END:
offset = self.size - offset offset = self.size - offset
whence = os.SEEK_SET whence = os.SEEK_SET
newOffset = filePtr.seek(offset, whence) if os.getenv('ANDROID_ROOT'):
newOffset = libc.lseek64(self.filePtr, offset, whence)
else:
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):
handle = None handle = None
info = None info = None
priorities = list() priorities = list()
openedFiles = list() openedFiles = list()
lastOpenedFile = None lastOpenedFile = None
shuttingDown = False shuttingDown = False
fileCounter = int() fileCounter = int()
progresses = list() progresses = list()
save_path = None save_path = None
def __init__(self, root, handle, startIndex): def __init__(self, root, handle):
self.root = root self.root = root
self.handle = handle self.handle = handle
self.waitForMetadata() self.waitForMetadata()
self.save_path = localize_path(self.root.torrentParams['save_path']) self.save_path = localize_path(self.root.torrentParams['save_path'])
self.priorities = list(self.handle.file_priorities()) self.priorities = list(self.handle.file_priorities())
file_ = self.__file_at_(startIndex) self.files = {}
self.files = {file_.name: file_}
#self.handle.set_piece_deadline(self.files[startIndex].startPiece, 50)
if startIndex < 0:
logging.info('No -file-index specified, downloading will be paused until any file is requested')
num_files = self.info.num_files() num_files = self.info.num_files()
for i in range(num_files): for i in range(num_files):
if startIndex == i: self.setPriority(i, 0)
self.setPriority(i, 1)
else: def file(self, index):
self.setPriority(i, 0) for name in list(self.files.keys()):
if self.files[name].index == index:
return self.files[name]
file_ = self.__file_at_(index)
self.files[file_.name] = file_
self.setPriority(index, 1)
return file_
def Shutdown(self): def Shutdown(self):
self.shuttingDown = True self.shuttingDown = True
@ -287,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)
@ -310,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_ = []
@ -330,29 +391,33 @@ 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)
fe_path = fileEntry.path fe_path = fileEntry.path
path = os.path.abspath(os.path.join(self.save_path, localize_path(fe_path))) path = os.path.abspath(os.path.join(self.save_path, localize_path(fe_path)))
return TorrentFile( return TorrentFile(
self, self,
fileEntry, fileEntry,
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:
@ -364,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)
@ -381,52 +447,68 @@ 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:
f.Seek(start_range, 0) f.Seek(start_range, 0)
chunk = f.piece_length chunk = f.piece_length
total = 0 total = 0
buf = bytearray(chunk) if os.getenv('ANDROID_ROOT'):
while chunk > 0: buf = create_string_buffer(chunk)
else:
buf = bytearray(chunk)
while chunk > 0 and not self.server.root_obj.forceShutdown:
if start_range + chunk > end_range: if start_range + chunk > end_range:
chunk = end_range - start_range chunk = end_range - start_range
buf = bytearray(chunk) if os.getenv('ANDROID_ROOT'):
buf = create_string_buffer(chunk)
else:
buf = bytearray(chunk)
try: try:
if f.Read(buf) < 1: break if f.Read(buf) < 1: break
self.wfile.write(buf) while self.server.root_obj.pause and not self.server.root_obj.forceShutdown:
time.sleep(0.1)
continue
if os.getenv('ANDROID_ROOT'):
self.wfile.write(buf.raw)
else:
self.wfile.write(buf)
except: except:
break break
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:
@ -453,27 +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):
def __init__(self, uri = '', bindAddress = 'localhost:5001', downloadPath = '.', pause = False
idleTimeout = -1, fileIndex = -1, keepComplete = False,
keepIncomplete = False, keepFiles = False, showAllStats = False, def __init__(self, uri='', bindAddress='localhost:5001', downloadPath='.',
showOverallProgress = False, showFilesProgress = False, idleTimeout=-1, keepComplete=False,
showPiecesProgress = False, debugAlerts = False, keepIncomplete=False, keepFiles=False, showAllStats=False,
exitOnFinish = False, resumeFile = '', stateFile = '', showOverallProgress=False, showFilesProgress=False,
userAgent = USER_AGENT, dhtRouters = '', trackers = '', showPiecesProgress=False, debugAlerts=False,
listenPort = 6881, torrentConnectBoost = 50, connectionSpeed = 50, exitOnFinish=False, resumeFile='', stateFile='',
peerConnectTimeout = 15, requestTimeout = 20, maxDownloadRate = -1, userAgent=USER_AGENT, dhtRouters='', trackers='',
maxUploadRate = -1, connectionsLimit = 200, encryption = 1, listenPort=6881, torrentConnectBoost=50, connectionSpeed=50,
minReconnectTime = 60, maxFailCount = 3, noSparseFile = False, peerConnectTimeout=15, requestTimeout=20, maxDownloadRate=-1,
randomPort = False, enableScrape = False, enableDHT = True, maxUploadRate=-1, connectionsLimit=200, encryption=1,
enableLSD = True, enableUPNP = True, enableNATPMP = True, enableUTP = True, enableTCP = True): minReconnectTime=60, maxFailCount=3, noSparseFile=False,
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
@ -484,7 +571,6 @@ class Pyrrent2http(object):
self.config.bindAddress = bindAddress self.config.bindAddress = bindAddress
self.config.downloadPath = downloadPath self.config.downloadPath = downloadPath
self.config.idleTimeout = idleTimeout self.config.idleTimeout = idleTimeout
self.config.fileIndex = fileIndex
self.config.keepComplete = keepComplete self.config.keepComplete = keepComplete
self.config.keepIncomplete = keepIncomplete self.config.keepIncomplete = keepIncomplete
self.config.keepFiles = keepFiles self.config.keepFiles = keepFiles
@ -519,6 +605,7 @@ class Pyrrent2http(object):
self.config.enableNATPMP = enableNATPMP self.config.enableNATPMP = enableNATPMP
self.config.enableUTP = enableUTP self.config.enableUTP = enableUTP
self.config.enableTCP = enableTCP self.config.enableTCP = enableTCP
self.config.proxy = proxy
if self.config.uri == '': if self.config.uri == '':
raise Exception("uri is empty string") raise Exception("uri is empty string")
if self.config.uri.startswith('magnet:'): if self.config.uri.startswith('magnet:'):
@ -567,12 +654,12 @@ class Pyrrent2http(object):
# #
self.torrentHandle.set_max_connections(60) self.torrentHandle.set_max_connections(60)
if self.config.trackers != '': if self.config.trackers != '':
trackers = self.config.trackers.split(',') trackers = self.config.trackers.split(',')
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(tracker, startTier + n) 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')
self.torrentHandle.scrape_tracker() self.torrentHandle.scrape_tracker()
@ -582,30 +669,24 @@ class Pyrrent2http(object):
info = self.torrentHandle.get_torrent_info() info = self.torrentHandle.get_torrent_info()
logging.info('Downloading torrent: %s' % (info.name(),)) logging.info('Downloading torrent: %s' % (info.name(),))
try: try:
self.TorrentFS = TorrentFS(self, self.torrentHandle, self.config.fileIndex) self.TorrentFS = TorrentFS(self, self.torrentHandle)
except Exception as e: except Exception as e:
logging.error(e.args) logging.error(e.args)
name = self.TorrentFS.info.name()
self.torrent_name = name
def startHTTP(self): def startHTTP(self):
#def http_server_loop(listener, alive):
# while alive.is_set():
# print('+++handle request+++')
# listener.handle_request()
# listener.server_close()
#self.main_alive = threading.Event()
#self.main_alive.set()
logging.info('Starting HTTP Server...') logging.info('Starting HTTP Server...')
handler = HttpHandlerFactory() handler = HttpHandlerFactory()
handler.protocol_version = 'HTTP/1.1'
logging.info('Listening HTTP on %s...\n' % (self.config.bindAddress,)) logging.info('Listening HTTP on %s...\n' % (self.config.bindAddress,))
host, strport = self.config.bindAddress.split(':') host, strport = self.config.bindAddress.split(':')
if len(strport) > 0: if len(strport) > 0:
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.httpListener.timeout = 0.5 self.listener_thread = threading.Thread(target=self.httpListener.serve_forever)
#thread = threading.Thread(target = http_server_loop, args = (self.httpListener, self.main_alive)) self.listener_thread.start()
thread = threading.Thread(target = self.httpListener.serve_forever)
thread.start()
def startServices(self): def startServices(self):
if self.config.enableDHT: if self.config.enableDHT:
@ -624,7 +705,7 @@ class Pyrrent2http(object):
def startSession(self): def startSession(self):
logging.info('Starting session...') logging.info('Starting session...')
self.session = lt.session(lt.fingerprint('LT', lt.version_major, lt.version_minor, 0, 0), self.session = lt.session(lt.fingerprint('LT', lt.version_major, lt.version_minor, 0, 0),
flags=int(lt.session_flags_t.add_default_plugins)) flags=int(lt.session_flags_t.add_default_plugins))
alertMask = (lt.alert.category_t.error_notification | alertMask = (lt.alert.category_t.error_notification |
lt.alert.category_t.storage_notification | lt.alert.category_t.storage_notification |
lt.alert.category_t.tracker_notification | lt.alert.category_t.tracker_notification |
@ -647,6 +728,20 @@ class Pyrrent2http(object):
settings["rate_limit_ip_overhead"] = True settings["rate_limit_ip_overhead"] = True
settings["min_announce_interval"] = 60 settings["min_announce_interval"] = 60
settings["tracker_backoff"] = 0 settings["tracker_backoff"] = 0
### Непонятно, как заставить использовать прокси только для подключения к трекеру?
if self.config.proxy is not None:
ps = lt.proxy_settings()
# peer_ps = lt.proxy_settings()
# peer_ps.type = lt.proxy_type.none
ps.hostname = self.config.proxy['host']
ps.port = self.config.proxy['port']
ps.type = lt.proxy_type.socks5
# self.session.set_peer_proxy(peer_ps)
self.session.set_proxy(ps)
settings['force_proxy'] = False
settings['proxy_peer_connections'] = False
settings['anonymous_mode'] = False
settings['proxy_tracker_connections'] = True
self.session.set_settings(settings) self.session.set_settings(settings)
if self.config.stateFile != '': if self.config.stateFile != '':
@ -715,98 +810,71 @@ class Pyrrent2http(object):
def Status(self): def Status(self):
info = self.TorrentFS.info info = self.TorrentFS.info
#tstatus = self.torrentHandle.status()
#tstatus = self.TorrentFS.handle.status()
tstatus = self.torrentHandle.status() tstatus = self.torrentHandle.status()
status = { status = {
'name' : info.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):
retFiles = {'files': []} def Ls(self, index):
fi = {}
if self.TorrentFS.HasTorrentInfo(): if self.TorrentFS.HasTorrentInfo():
x = [n for n in list(self.TorrentFS.files.keys()) if self.TorrentFS.files[n].index == index]
name = x[0]
files = self.TorrentFS.files files = self.TorrentFS.files
for name in files.keys(): Url = 'http://' + self.config.bindAddress + '/files/' + urllib.parse.quote(name)
Url = 'http://' + self.config.bindAddress + '/files/' + urllib.quote(name) fi = {
fi = { 'index': files[name].index,
'index': files[name].index, 'name': files[name].unicode_name,
'name': files[name].unicode_name, 'media_type': files[name].media_type,
'media_type': files[name].media_type, 'size': files[name].size,
'size': files[name].size, 'offset': files[name].offset,
'offset': files[name].offset, 'download': files[name].downloaded,
'download': files[name].downloaded, 'progress': files[name].progress,
'progress': files[name].progress, 'save_path': files[name].save_path,
'save_path': files[name].save_path, 'url': Url
'url': Url }
} return fi
retFiles['files'].append(fi)
return retFiles
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():
if peer.flags & peer.connecting or peer.flags & peer.handshake: if peer.flags & peer.connecting or peer.flags & peer.handshake:
continue continue
pi = { pi = {
'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,
'Client': peer.client 'Client': peer.client
} }
peers['peers'].append(pi) peers['peers'].append(pi)
return peers return peers
'''def stats(self):
status = self.torrentHandle.status()
dhtStatusStr = ''
if not status.has_metadata:
return
if self.config.showAllStats or self.config.showOverallProgress:
sessionStatus = self.session.status()
if self.session.is_dht_running():
dhtStatusStr = ', DHT nodes: %d' % (sessionStatus.dht_nodes,)
errorStr = ''
if len(status.error) > 0:
errorStr = ' (%s)' % (status.error,)
logging.info('%s, overall progress: %.2f%%, dl/ul: %.3f/%.3f kbps, peers/seeds: %d/%d' % (
str(status.state),
status.progress * 100,
float(status.download_rate)/1024,
float(status.upload_rate)/1024,
status.num_peers,
status.num_seeds
) + dhtStatusStr + errorStr
)
if self.config.showFilesProgress or self.config.showAllStats:
str_ = 'Files: '
for i, f in enumerate(self.TorrentFS.files):
str_ += '[%d] %.2f%% ' % (i, f.Progress()*100)
logging.info(str_)
if (self.config.showPiecesProgress or self.config.showAllStats) and self.TorrentFS.lastOpenedFile != None:
self.TorrentFS.lastOpenedFile.ShowPieces()
'''
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:
@ -817,8 +885,8 @@ 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.statsTicker = Ticker(30)
self.saveResumeDataTicker = Ticker(5) self.saveResumeDataTicker = Ticker(5)
time_start = time.time() time_start = time.time()
while True: while True:
@ -833,8 +901,6 @@ class Pyrrent2http(object):
if os.getppid() == 1: if os.getppid() == 1:
self.forceShutdown = True self.forceShutdown = True
time_start = time.time() time_start = time.time()
#if self.statsTicker.true:
# self.stats()
if self.saveResumeDataTicker.true: if self.saveResumeDataTicker.true:
self.saveResumeData(True) self.saveResumeData(True)
time.sleep(0.3) time.sleep(0.3)
@ -848,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
@ -871,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:
@ -886,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():
@ -896,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
@ -911,12 +982,13 @@ 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
#self.statsTicker.stop()
self.saveResumeDataTicker.stop() self.saveResumeDataTicker.stop()
self.httpListener.shutdown() self.httpListener.shutdown()
self.httpListener.socket.close()
self.TorrentFS.Shutdown() self.TorrentFS.Shutdown()
if self.session != None: if self.session != None:
self.session.pause() self.session.pause()
@ -926,5 +998,5 @@ class Pyrrent2http(object):
self.saveSessionState() self.saveSessionState()
self.removeTorrent() self.removeTorrent()
logging.info('Aborting the session') logging.info('Aborting the session')
del self.session self.session = None
logging.info('Bye bye') logging.info('Bye bye')

View File

@ -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

View File

@ -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(True 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(True 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')