Compare commits
No commits in common. "master" and "sandbox1" have entirely different histories.
|
@ -4,6 +4,3 @@ 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 работает на множестве платформ.
|
|
||||||
|
|
|
@ -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="1.1.1" provider-name="inpos">
|
<addon id="script.module.pyrrent2http" name="pyrrent2http" version="0.9.0" provider-name="inpos">
|
||||||
<requires>
|
<requires>
|
||||||
<import addon="xbmc.python" version="3.0.0"/>
|
<import addon="xbmc.python" version="2.14.0"/>
|
||||||
<import addon="script.module.libtorrent" version="1.2.0"/>
|
<import addon="script.module.libtorrent" />
|
||||||
<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://git.ukamnya.ru/ukamnya/script.module.pyrrent2http</source></extension>
|
<source>https://github.com/inpos/script.module.pyrrent2http</source></extension>
|
||||||
</addon>
|
</addon>
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
[B]Version 1.0.0[/B]
|
|
||||||
+ Исправлен механизм добавления трекеров
|
|
||||||
+ Добавлена поддержка прокси SOCKS длч подключения к трекерам
|
|
||||||
|
|
||||||
[B]Version 0.9.0[/B]
|
[B]Version 0.9.0[/B]
|
||||||
+ Изменён принцип работы с торрентом
|
+ Изменён принцип работы с торрентом
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,33 @@
|
||||||
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")
|
||||||
|
@ -12,5 +39,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,37 +1,31 @@
|
||||||
# -*- 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
|
from . import SessionStatus, FileStatus, PeerInfo, Encryption
|
||||||
from . import pyrrent2http
|
from util import can_bind, find_free_port, localize_path, uri2path, detect_media_type
|
||||||
from .error import Error
|
import threading
|
||||||
from .structs import Encryption
|
import urllib
|
||||||
from .util import can_bind, find_free_port, localize_path, uri2path, detect_media_type
|
import chardet
|
||||||
|
|
||||||
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=200, download_kbps=-1, upload_kbps=-1,
|
bind_host='127.0.0.1', bind_port=5001, connections_limit=None, download_kbps=None, upload_kbps=None,
|
||||||
enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False,
|
enable_dht=True, enable_lsd=True, enable_natpmp=True, enable_upnp=True, enable_scrape=False,
|
||||||
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,
|
||||||
|
@ -39,7 +33,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, proxy=None):
|
dht_routers=None, trackers=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.
|
||||||
|
@ -132,7 +126,6 @@ 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):
|
||||||
|
@ -173,7 +166,6 @@ 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,
|
||||||
|
@ -204,7 +196,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,
|
||||||
|
@ -214,19 +206,15 @@ 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)
|
||||||
|
@ -234,9 +222,10 @@ 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
|
||||||
|
@ -245,7 +234,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:
|
||||||
|
@ -256,15 +245,6 @@ 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.
|
||||||
|
@ -289,6 +269,8 @@ 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).
|
||||||
|
@ -304,9 +286,8 @@ 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 = [fs for fs in res if fs.media_type in media_types]
|
res = filter(lambda fs: fs.media_type in media_types, res)
|
||||||
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))
|
||||||
|
@ -315,21 +296,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.parse.quote(f.path)
|
Url = 'http://' + "%s:%s" % (self.bind_host, self.bind_port) + '/files/' + urllib.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 is not None and detect_media_type(f.path) or '',
|
'media_type': media_types and detect_media_type(f.path.decode(chardet.detect(f.path)['encoding'])) or '',
|
||||||
'download': 0,
|
'download': 0,
|
||||||
'progress': 0.0,
|
'progress': 0.0,
|
||||||
'save_path': '',
|
'save_path': '',
|
||||||
'url': Url
|
'url': Url
|
||||||
})
|
})
|
||||||
if len(files) > 0:
|
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 = [fs for fs in res if fs.media_type in media_types]
|
res = filter(lambda fs: fs.media_type in media_types, res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def file_status(self, file_index, timeout=10):
|
def file_status(self, file_index, timeout=10):
|
||||||
|
@ -343,10 +324,11 @@ class Engine:
|
||||||
:return: File with specified index
|
:return: File with specified index
|
||||||
:rtype: FileStatus
|
:rtype: FileStatus
|
||||||
"""
|
"""
|
||||||
filestatus = self.pyrrent2http.Ls(file_index)
|
files = self.pyrrent2http.Ls()['files']
|
||||||
try:
|
if files:
|
||||||
return FileStatus(**filestatus)
|
for f in files:
|
||||||
except:
|
if f['index'] == file_index:
|
||||||
|
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)
|
||||||
|
|
||||||
|
@ -364,7 +346,6 @@ 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
|
||||||
|
|
|
@ -0,0 +1,613 @@
|
||||||
|
"""Guess the MIME type of a file.
|
||||||
|
|
||||||
|
This module defines two useful functions:
|
||||||
|
|
||||||
|
guess_type(url, strict=1) -- guess the MIME type and encoding of a URL.
|
||||||
|
|
||||||
|
guess_extension(type, strict=1) -- guess the extension for a given MIME type.
|
||||||
|
|
||||||
|
It also contains the following, for tuning the behavior:
|
||||||
|
|
||||||
|
Data:
|
||||||
|
|
||||||
|
knownfiles -- list of files to parse
|
||||||
|
inited -- flag set when init() has been called
|
||||||
|
suffix_map -- dictionary mapping suffixes to suffixes
|
||||||
|
encodings_map -- dictionary mapping suffixes to encodings
|
||||||
|
types_map -- dictionary mapping suffixes to types
|
||||||
|
|
||||||
|
Functions:
|
||||||
|
|
||||||
|
init([files]) -- parse a list of files, default knownfiles (on Windows, the
|
||||||
|
default values are taken from the registry)
|
||||||
|
read_mime_types(file) -- parse one file, return a dictionary or None
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import posixpath
|
||||||
|
import urllib
|
||||||
|
try:
|
||||||
|
import _winreg
|
||||||
|
except ImportError:
|
||||||
|
_winreg = None
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"guess_type","guess_extension","guess_all_extensions",
|
||||||
|
"add_type","read_mime_types","init"
|
||||||
|
]
|
||||||
|
|
||||||
|
knownfiles = [
|
||||||
|
"/etc/mime.types",
|
||||||
|
"/etc/httpd/mime.types", # Mac OS X
|
||||||
|
"/etc/httpd/conf/mime.types", # Apache
|
||||||
|
"/etc/apache/mime.types", # Apache 1
|
||||||
|
"/etc/apache2/mime.types", # Apache 2
|
||||||
|
"/usr/local/etc/httpd/conf/mime.types",
|
||||||
|
"/usr/local/lib/netscape/mime.types",
|
||||||
|
"/usr/local/etc/httpd/conf/mime.types", # Apache 1.2
|
||||||
|
"/usr/local/etc/mime.types", # Apache 1.3
|
||||||
|
]
|
||||||
|
|
||||||
|
inited = False
|
||||||
|
_db = None
|
||||||
|
|
||||||
|
|
||||||
|
class MimeTypes:
|
||||||
|
"""MIME-types datastore.
|
||||||
|
|
||||||
|
This datastore can handle information from mime.types-style files
|
||||||
|
and supports basic determination of MIME type from a filename or
|
||||||
|
URL, and can guess a reasonable extension given a MIME type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, filenames=(), strict=True):
|
||||||
|
if not inited:
|
||||||
|
init()
|
||||||
|
self.encodings_map = encodings_map.copy()
|
||||||
|
self.suffix_map = suffix_map.copy()
|
||||||
|
self.types_map = ({}, {}) # dict for (non-strict, strict)
|
||||||
|
self.types_map_inv = ({}, {})
|
||||||
|
for (ext, type) in types_map.items():
|
||||||
|
self.add_type(type, ext, True)
|
||||||
|
for (ext, type) in common_types.items():
|
||||||
|
self.add_type(type, ext, False)
|
||||||
|
for name in filenames:
|
||||||
|
self.read(name, strict)
|
||||||
|
|
||||||
|
def add_type(self, type, ext, strict=True):
|
||||||
|
"""Add a mapping between a type and an extension.
|
||||||
|
|
||||||
|
When the extension is already known, the new
|
||||||
|
type will replace the old one. When the type
|
||||||
|
is already known the extension will be added
|
||||||
|
to the list of known extensions.
|
||||||
|
|
||||||
|
If strict is true, information will be added to
|
||||||
|
list of standard types, else to the list of non-standard
|
||||||
|
types.
|
||||||
|
"""
|
||||||
|
self.types_map[strict][ext] = type
|
||||||
|
exts = self.types_map_inv[strict].setdefault(type, [])
|
||||||
|
if ext not in exts:
|
||||||
|
exts.append(ext)
|
||||||
|
|
||||||
|
def guess_type(self, url, strict=True):
|
||||||
|
"""Guess the type of a file based on its URL.
|
||||||
|
|
||||||
|
Return value is a tuple (type, encoding) where type is None if
|
||||||
|
the type can't be guessed (no or unknown suffix) or a string
|
||||||
|
of the form type/subtype, usable for a MIME Content-type
|
||||||
|
header; and encoding is None for no encoding or the name of
|
||||||
|
the program used to encode (e.g. compress or gzip). The
|
||||||
|
mappings are table driven. Encoding suffixes are case
|
||||||
|
sensitive; type suffixes are first tried case sensitive, then
|
||||||
|
case insensitive.
|
||||||
|
|
||||||
|
The suffixes .tgz, .taz and .tz (case sensitive!) are all
|
||||||
|
mapped to '.tar.gz'. (This is table-driven too, using the
|
||||||
|
dictionary suffix_map.)
|
||||||
|
|
||||||
|
Optional `strict' argument when False adds a bunch of commonly found,
|
||||||
|
but non-standard types.
|
||||||
|
"""
|
||||||
|
scheme, url = urllib.splittype(url)
|
||||||
|
if scheme == 'data':
|
||||||
|
# syntax of data URLs:
|
||||||
|
# dataurl := "data:" [ mediatype ] [ ";base64" ] "," data
|
||||||
|
# mediatype := [ type "/" subtype ] *( ";" parameter )
|
||||||
|
# data := *urlchar
|
||||||
|
# parameter := attribute "=" value
|
||||||
|
# type/subtype defaults to "text/plain"
|
||||||
|
comma = url.find(',')
|
||||||
|
if comma < 0:
|
||||||
|
# bad data URL
|
||||||
|
return None, None
|
||||||
|
semi = url.find(';', 0, comma)
|
||||||
|
if semi >= 0:
|
||||||
|
type = url[:semi]
|
||||||
|
else:
|
||||||
|
type = url[:comma]
|
||||||
|
if '=' in type or '/' not in type:
|
||||||
|
type = 'text/plain'
|
||||||
|
return type, None # never compressed, so encoding is None
|
||||||
|
base, ext = posixpath.splitext(url)
|
||||||
|
while ext in self.suffix_map:
|
||||||
|
base, ext = posixpath.splitext(base + self.suffix_map[ext])
|
||||||
|
if ext in self.encodings_map:
|
||||||
|
encoding = self.encodings_map[ext]
|
||||||
|
base, ext = posixpath.splitext(base)
|
||||||
|
else:
|
||||||
|
encoding = None
|
||||||
|
types_map = self.types_map[True]
|
||||||
|
if ext in types_map:
|
||||||
|
return types_map[ext], encoding
|
||||||
|
elif ext.lower() in types_map:
|
||||||
|
return types_map[ext.lower()], encoding
|
||||||
|
elif strict:
|
||||||
|
return None, encoding
|
||||||
|
types_map = self.types_map[False]
|
||||||
|
if ext in types_map:
|
||||||
|
return types_map[ext], encoding
|
||||||
|
elif ext.lower() in types_map:
|
||||||
|
return types_map[ext.lower()], encoding
|
||||||
|
else:
|
||||||
|
return None, encoding
|
||||||
|
|
||||||
|
def guess_all_extensions(self, type, strict=True):
|
||||||
|
"""Guess the extensions for a file based on its MIME type.
|
||||||
|
|
||||||
|
Return value is a list of strings giving the possible filename
|
||||||
|
extensions, including the leading dot ('.'). The extension is not
|
||||||
|
guaranteed to have been associated with any particular data stream,
|
||||||
|
but would be mapped to the MIME type `type' by guess_type().
|
||||||
|
|
||||||
|
Optional `strict' argument when false adds a bunch of commonly found,
|
||||||
|
but non-standard types.
|
||||||
|
"""
|
||||||
|
type = type.lower()
|
||||||
|
extensions = self.types_map_inv[True].get(type, [])
|
||||||
|
if not strict:
|
||||||
|
for ext in self.types_map_inv[False].get(type, []):
|
||||||
|
if ext not in extensions:
|
||||||
|
extensions.append(ext)
|
||||||
|
return extensions
|
||||||
|
|
||||||
|
def guess_extension(self, type, strict=True):
|
||||||
|
"""Guess the extension for a file based on its MIME type.
|
||||||
|
|
||||||
|
Return value is a string giving a filename extension,
|
||||||
|
including the leading dot ('.'). The extension is not
|
||||||
|
guaranteed to have been associated with any particular data
|
||||||
|
stream, but would be mapped to the MIME type `type' by
|
||||||
|
guess_type(). If no extension can be guessed for `type', None
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
Optional `strict' argument when false adds a bunch of commonly found,
|
||||||
|
but non-standard types.
|
||||||
|
"""
|
||||||
|
extensions = self.guess_all_extensions(type, strict)
|
||||||
|
if not extensions:
|
||||||
|
return None
|
||||||
|
return extensions[0]
|
||||||
|
|
||||||
|
def read(self, filename, strict=True):
|
||||||
|
"""
|
||||||
|
Read a single mime.types-format file, specified by pathname.
|
||||||
|
|
||||||
|
If strict is true, information will be added to
|
||||||
|
list of standard types, else to the list of non-standard
|
||||||
|
types.
|
||||||
|
"""
|
||||||
|
with open(filename) as fp:
|
||||||
|
self.readfp(fp, strict)
|
||||||
|
|
||||||
|
def readfp(self, fp, strict=True):
|
||||||
|
"""
|
||||||
|
Read a single mime.types-format file.
|
||||||
|
|
||||||
|
If strict is true, information will be added to
|
||||||
|
list of standard types, else to the list of non-standard
|
||||||
|
types.
|
||||||
|
"""
|
||||||
|
while 1:
|
||||||
|
line = fp.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
words = line.split()
|
||||||
|
for i in range(len(words)):
|
||||||
|
if words[i][0] == '#':
|
||||||
|
del words[i:]
|
||||||
|
break
|
||||||
|
if not words:
|
||||||
|
continue
|
||||||
|
type, suffixes = words[0], words[1:]
|
||||||
|
for suff in suffixes:
|
||||||
|
self.add_type(type, '.' + suff, strict)
|
||||||
|
|
||||||
|
def read_windows_registry(self, strict=True):
|
||||||
|
"""
|
||||||
|
Load the MIME types database from Windows registry.
|
||||||
|
|
||||||
|
If strict is true, information will be added to
|
||||||
|
list of standard types, else to the list of non-standard
|
||||||
|
types.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Windows only
|
||||||
|
if not _winreg:
|
||||||
|
return
|
||||||
|
|
||||||
|
def enum_types(mimedb):
|
||||||
|
i = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
ctype = _winreg.EnumKey(mimedb, i)
|
||||||
|
except EnvironmentError:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
ctype = ctype#.encode(default_encoding) # omit in 3.x!
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
yield ctype
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
default_encoding = sys.getdefaultencoding()
|
||||||
|
with _winreg.OpenKey(_winreg.HKEY_CLASSES_ROOT,
|
||||||
|
r'MIME\Database\Content Type') as mimedb:
|
||||||
|
for ctype in enum_types(mimedb):
|
||||||
|
try:
|
||||||
|
with _winreg.OpenKey(mimedb, ctype) as key:
|
||||||
|
suffix, datatype = _winreg.QueryValueEx(key,
|
||||||
|
'Extension')
|
||||||
|
except EnvironmentError:
|
||||||
|
continue
|
||||||
|
if datatype != _winreg.REG_SZ:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
suffix = suffix.encode(default_encoding) # omit in 3.x!
|
||||||
|
except UnicodeEncodeError:
|
||||||
|
continue
|
||||||
|
self.add_type(ctype, suffix, strict)
|
||||||
|
|
||||||
|
|
||||||
|
def guess_type(url, strict=True):
|
||||||
|
"""Guess the type of a file based on its URL.
|
||||||
|
|
||||||
|
Return value is a tuple (type, encoding) where type is None if the
|
||||||
|
type can't be guessed (no or unknown suffix) or a string of the
|
||||||
|
form type/subtype, usable for a MIME Content-type header; and
|
||||||
|
encoding is None for no encoding or the name of the program used
|
||||||
|
to encode (e.g. compress or gzip). The mappings are table
|
||||||
|
driven. Encoding suffixes are case sensitive; type suffixes are
|
||||||
|
first tried case sensitive, then case insensitive.
|
||||||
|
|
||||||
|
The suffixes .tgz, .taz and .tz (case sensitive!) are all mapped
|
||||||
|
to ".tar.gz". (This is table-driven too, using the dictionary
|
||||||
|
suffix_map).
|
||||||
|
|
||||||
|
Optional `strict' argument when false adds a bunch of commonly found, but
|
||||||
|
non-standard types.
|
||||||
|
"""
|
||||||
|
if _db is None:
|
||||||
|
init()
|
||||||
|
return _db.guess_type(url, strict)
|
||||||
|
|
||||||
|
|
||||||
|
def guess_all_extensions(type, strict=True):
|
||||||
|
"""Guess the extensions for a file based on its MIME type.
|
||||||
|
|
||||||
|
Return value is a list of strings giving the possible filename
|
||||||
|
extensions, including the leading dot ('.'). The extension is not
|
||||||
|
guaranteed to have been associated with any particular data
|
||||||
|
stream, but would be mapped to the MIME type `type' by
|
||||||
|
guess_type(). If no extension can be guessed for `type', None
|
||||||
|
is returned.
|
||||||
|
|
||||||
|
Optional `strict' argument when false adds a bunch of commonly found,
|
||||||
|
but non-standard types.
|
||||||
|
"""
|
||||||
|
if _db is None:
|
||||||
|
init()
|
||||||
|
return _db.guess_all_extensions(type, strict)
|
||||||
|
|
||||||
|
def guess_extension(type, strict=True):
|
||||||
|
"""Guess the extension for a file based on its MIME type.
|
||||||
|
|
||||||
|
Return value is a string giving a filename extension, including the
|
||||||
|
leading dot ('.'). The extension is not guaranteed to have been
|
||||||
|
associated with any particular data stream, but would be mapped to the
|
||||||
|
MIME type `type' by guess_type(). If no extension can be guessed for
|
||||||
|
`type', None is returned.
|
||||||
|
|
||||||
|
Optional `strict' argument when false adds a bunch of commonly found,
|
||||||
|
but non-standard types.
|
||||||
|
"""
|
||||||
|
if _db is None:
|
||||||
|
init()
|
||||||
|
return _db.guess_extension(type, strict)
|
||||||
|
|
||||||
|
def add_type(type, ext, strict=True):
|
||||||
|
"""Add a mapping between a type and an extension.
|
||||||
|
|
||||||
|
When the extension is already known, the new
|
||||||
|
type will replace the old one. When the type
|
||||||
|
is already known the extension will be added
|
||||||
|
to the list of known extensions.
|
||||||
|
|
||||||
|
If strict is true, information will be added to
|
||||||
|
list of standard types, else to the list of non-standard
|
||||||
|
types.
|
||||||
|
"""
|
||||||
|
if _db is None:
|
||||||
|
init()
|
||||||
|
return _db.add_type(type, ext, strict)
|
||||||
|
|
||||||
|
|
||||||
|
def init(files=None):
|
||||||
|
global suffix_map, types_map, encodings_map, common_types
|
||||||
|
global inited, _db
|
||||||
|
inited = True # so that MimeTypes.__init__() doesn't call us again
|
||||||
|
db = MimeTypes()
|
||||||
|
if files is None:
|
||||||
|
if _winreg:
|
||||||
|
db.read_windows_registry()
|
||||||
|
files = knownfiles
|
||||||
|
for file in files:
|
||||||
|
if os.path.isfile(file):
|
||||||
|
db.read(file)
|
||||||
|
encodings_map = db.encodings_map
|
||||||
|
suffix_map = db.suffix_map
|
||||||
|
types_map = db.types_map[True]
|
||||||
|
common_types = db.types_map[False]
|
||||||
|
# Make the DB a global variable now that it is fully initialized
|
||||||
|
_db = db
|
||||||
|
|
||||||
|
|
||||||
|
def read_mime_types(file):
|
||||||
|
try:
|
||||||
|
f = open(file)
|
||||||
|
except IOError:
|
||||||
|
return None
|
||||||
|
db = MimeTypes()
|
||||||
|
db.readfp(f, True)
|
||||||
|
return db.types_map[True]
|
||||||
|
|
||||||
|
|
||||||
|
def _default_mime_types():
|
||||||
|
global suffix_map
|
||||||
|
global encodings_map
|
||||||
|
global types_map
|
||||||
|
global common_types
|
||||||
|
|
||||||
|
suffix_map = {
|
||||||
|
'.tgz': '.tar.gz',
|
||||||
|
'.taz': '.tar.gz',
|
||||||
|
'.tz': '.tar.gz',
|
||||||
|
'.tbz2': '.tar.bz2',
|
||||||
|
}
|
||||||
|
|
||||||
|
encodings_map = {
|
||||||
|
'.gz': 'gzip',
|
||||||
|
'.Z': 'compress',
|
||||||
|
'.bz2': 'bzip2',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Before adding new types, make sure they are either registered with IANA,
|
||||||
|
# at http://www.isi.edu/in-notes/iana/assignments/media-types
|
||||||
|
# or extensions, i.e. using the x- prefix
|
||||||
|
|
||||||
|
# If you add to these, please keep them sorted!
|
||||||
|
types_map = {
|
||||||
|
'.3gp' : 'video/3gp',
|
||||||
|
'.a' : 'application/octet-stream',
|
||||||
|
'.ai' : 'application/postscript',
|
||||||
|
'.aif' : 'audio/x-aiff',
|
||||||
|
'.aifc' : 'audio/x-aiff',
|
||||||
|
'.aiff' : 'audio/x-aiff',
|
||||||
|
'.asf' : 'video/x-ms-asf',
|
||||||
|
'.asx' : 'video/x-ms-asf',
|
||||||
|
'.au' : 'audio/basic',
|
||||||
|
'.avi' : 'video/x-msvideo',
|
||||||
|
'.axv' : 'video/annodex',
|
||||||
|
'.bat' : 'text/plain',
|
||||||
|
'.bcpio' : 'application/x-bcpio',
|
||||||
|
'.bin' : 'application/octet-stream',
|
||||||
|
'.bmp' : 'image/x-ms-bmp',
|
||||||
|
'.c' : 'text/plain',
|
||||||
|
# Duplicates :(
|
||||||
|
'.cdf' : 'application/x-cdf',
|
||||||
|
'.cdf' : 'application/x-netcdf',
|
||||||
|
'.cpio' : 'application/x-cpio',
|
||||||
|
'.csh' : 'application/x-csh',
|
||||||
|
'.css' : 'text/css',
|
||||||
|
'.dif' : 'video/dv',
|
||||||
|
'.dl' : 'video/dl',
|
||||||
|
'.dll' : 'application/octet-stream',
|
||||||
|
'.dv' : 'video/dv',
|
||||||
|
'.doc' : 'application/msword',
|
||||||
|
'.dot' : 'application/msword',
|
||||||
|
'.dvi' : 'application/x-dvi',
|
||||||
|
'.eml' : 'message/rfc822',
|
||||||
|
'.eps' : 'application/postscript',
|
||||||
|
'.etx' : 'text/x-setext',
|
||||||
|
'.exe' : 'application/octet-stream',
|
||||||
|
'.fli' : 'video/fli',
|
||||||
|
'.flv' : 'video/x-flv',
|
||||||
|
'.gif' : 'image/gif',
|
||||||
|
'.gl' : 'video/gl',
|
||||||
|
'.gtar' : 'application/x-gtar',
|
||||||
|
'.h' : 'text/plain',
|
||||||
|
'.hdf' : 'application/x-hdf',
|
||||||
|
'.htm' : 'text/html',
|
||||||
|
'.html' : 'text/html',
|
||||||
|
'.ief' : 'image/ief',
|
||||||
|
'.jpe' : 'image/jpeg',
|
||||||
|
'.jpeg' : 'image/jpeg',
|
||||||
|
'.jpg' : 'image/jpeg',
|
||||||
|
'.js' : 'application/x-javascript',
|
||||||
|
'.ksh' : 'text/plain',
|
||||||
|
'.latex' : 'application/x-latex',
|
||||||
|
'.lsf' : 'video/x-la-lsf',
|
||||||
|
'.lsx' : 'video/x-la-lsf',
|
||||||
|
'.m1v' : 'video/mpeg',
|
||||||
|
'.man' : 'application/x-troff-man',
|
||||||
|
'.me' : 'application/x-troff-me',
|
||||||
|
'.mht' : 'message/rfc822',
|
||||||
|
'.mhtml' : 'message/rfc822',
|
||||||
|
'.mif' : 'application/x-mif',
|
||||||
|
'.mng' : 'video/x-mng',
|
||||||
|
'.movie' : 'video/x-sgi-movie',
|
||||||
|
'.mp2' : 'audio/mpeg',
|
||||||
|
'.mp3' : 'audio/mpeg',
|
||||||
|
'.mp4' : 'video/mp4',
|
||||||
|
'.mpa' : 'video/mpeg',
|
||||||
|
'.mpe' : 'video/mpeg',
|
||||||
|
'.mpeg' : 'video/mpeg',
|
||||||
|
'.mpg' : 'video/mpeg',
|
||||||
|
'.mpv' : 'video/matroska',
|
||||||
|
'.mkv' : 'video/matroska',
|
||||||
|
'.mov' : 'video/quicktime',
|
||||||
|
'.ms' : 'application/x-troff-ms',
|
||||||
|
'.nc' : 'application/x-netcdf',
|
||||||
|
'.nws' : 'message/rfc822',
|
||||||
|
'.o' : 'application/octet-stream',
|
||||||
|
'.obj' : 'application/octet-stream',
|
||||||
|
'.oda' : 'application/oda',
|
||||||
|
'.ogv' : 'video/ogg',
|
||||||
|
'.p12' : 'application/x-pkcs12',
|
||||||
|
'.p7c' : 'application/pkcs7-mime',
|
||||||
|
'.pbm' : 'image/x-portable-bitmap',
|
||||||
|
'.pdf' : 'application/pdf',
|
||||||
|
'.pfx' : 'application/x-pkcs12',
|
||||||
|
'.pgm' : 'image/x-portable-graymap',
|
||||||
|
'.pl' : 'text/plain',
|
||||||
|
'.png' : 'image/png',
|
||||||
|
'.pnm' : 'image/x-portable-anymap',
|
||||||
|
'.pot' : 'application/vnd.ms-powerpoint',
|
||||||
|
'.ppa' : 'application/vnd.ms-powerpoint',
|
||||||
|
'.ppm' : 'image/x-portable-pixmap',
|
||||||
|
'.pps' : 'application/vnd.ms-powerpoint',
|
||||||
|
'.ppt' : 'application/vnd.ms-powerpoint',
|
||||||
|
'.ps' : 'application/postscript',
|
||||||
|
'.pwz' : 'application/vnd.ms-powerpoint',
|
||||||
|
'.py' : 'text/x-python',
|
||||||
|
'.pyc' : 'application/x-python-code',
|
||||||
|
'.pyo' : 'application/x-python-code',
|
||||||
|
'.qt' : 'video/quicktime',
|
||||||
|
'.ra' : 'audio/x-pn-realaudio',
|
||||||
|
'.ram' : 'application/x-pn-realaudio',
|
||||||
|
'.ras' : 'image/x-cmu-raster',
|
||||||
|
'.rdf' : 'application/xml',
|
||||||
|
'.rgb' : 'image/x-rgb',
|
||||||
|
'.roff' : 'application/x-troff',
|
||||||
|
'.rtx' : 'text/richtext',
|
||||||
|
'.sgm' : 'text/x-sgml',
|
||||||
|
'.sgml' : 'text/x-sgml',
|
||||||
|
'.sh' : 'application/x-sh',
|
||||||
|
'.shar' : 'application/x-shar',
|
||||||
|
'.snd' : 'audio/basic',
|
||||||
|
'.so' : 'application/octet-stream',
|
||||||
|
'.src' : 'application/x-wais-source',
|
||||||
|
'.sv4cpio': 'application/x-sv4cpio',
|
||||||
|
'.sv4crc' : 'application/x-sv4crc',
|
||||||
|
'.swf' : 'application/x-shockwave-flash',
|
||||||
|
'.t' : 'application/x-troff',
|
||||||
|
'.tar' : 'application/x-tar',
|
||||||
|
'.tcl' : 'application/x-tcl',
|
||||||
|
'.tex' : 'application/x-tex',
|
||||||
|
'.texi' : 'application/x-texinfo',
|
||||||
|
'.texinfo': 'application/x-texinfo',
|
||||||
|
'.tif' : 'image/tiff',
|
||||||
|
'.tiff' : 'image/tiff',
|
||||||
|
'.tr' : 'application/x-troff',
|
||||||
|
'.ts' : 'video/MP2T',
|
||||||
|
'.tsv' : 'text/tab-separated-values',
|
||||||
|
'.txt' : 'text/plain',
|
||||||
|
'.ustar' : 'application/x-ustar',
|
||||||
|
'.vcf' : 'text/x-vcard',
|
||||||
|
'.wav' : 'audio/x-wav',
|
||||||
|
'.webm' : 'video/webm',
|
||||||
|
'.wiz' : 'application/msword',
|
||||||
|
'.wm' : 'video/x-ms-wm',
|
||||||
|
'.wmv' : 'video/x-ms-wmv',
|
||||||
|
'.wmx' : 'video/x-ms-wmx',
|
||||||
|
'.wvx' : 'video/x-ms-wvx',
|
||||||
|
'.wsdl' : 'application/xml',
|
||||||
|
'.xbm' : 'image/x-xbitmap',
|
||||||
|
'.xlb' : 'application/vnd.ms-excel',
|
||||||
|
# Duplicates :(
|
||||||
|
'.xls' : 'application/excel',
|
||||||
|
'.xls' : 'application/vnd.ms-excel',
|
||||||
|
'.xml' : 'text/xml',
|
||||||
|
'.xpdl' : 'application/xml',
|
||||||
|
'.xpm' : 'image/x-xpixmap',
|
||||||
|
'.xsl' : 'application/xml',
|
||||||
|
'.xwd' : 'image/x-xwindowdump',
|
||||||
|
'.zip' : 'application/zip',
|
||||||
|
}
|
||||||
|
|
||||||
|
# These are non-standard types, commonly found in the wild. They will
|
||||||
|
# only match if strict=0 flag is given to the API methods.
|
||||||
|
|
||||||
|
# Please sort these too
|
||||||
|
common_types = {
|
||||||
|
'.jpg' : 'image/jpg',
|
||||||
|
'.mid' : 'audio/midi',
|
||||||
|
'.midi': 'audio/midi',
|
||||||
|
'.pct' : 'image/pict',
|
||||||
|
'.pic' : 'image/pict',
|
||||||
|
'.pict': 'image/pict',
|
||||||
|
'.rtf' : 'application/rtf',
|
||||||
|
'.xul' : 'text/xul'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_default_mime_types()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import getopt
|
||||||
|
|
||||||
|
USAGE = """\
|
||||||
|
Usage: mimetypes.py [options] type
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--help / -h -- print this message and exit
|
||||||
|
--lenient / -l -- additionally search of some common, but non-standard
|
||||||
|
types.
|
||||||
|
--extension / -e -- guess extension instead of type
|
||||||
|
|
||||||
|
More than one type argument may be given.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def usage(code, msg=''):
|
||||||
|
print USAGE
|
||||||
|
if msg: print msg
|
||||||
|
sys.exit(code)
|
||||||
|
|
||||||
|
try:
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], 'hle',
|
||||||
|
['help', 'lenient', 'extension'])
|
||||||
|
except getopt.error, msg:
|
||||||
|
usage(1, msg)
|
||||||
|
|
||||||
|
strict = 1
|
||||||
|
extension = 0
|
||||||
|
for opt, arg in opts:
|
||||||
|
if opt in ('-h', '--help'):
|
||||||
|
usage(0)
|
||||||
|
elif opt in ('-l', '--lenient'):
|
||||||
|
strict = 0
|
||||||
|
elif opt in ('-e', '--extension'):
|
||||||
|
extension = 1
|
||||||
|
for gtype in args:
|
||||||
|
if extension:
|
||||||
|
guess = guess_extension(gtype, strict)
|
||||||
|
if not guess: print "I don't know anything about type", gtype
|
||||||
|
else: print guess
|
||||||
|
else:
|
||||||
|
guess, encoding = guess_type(gtype, strict)
|
||||||
|
if not guess: print "I don't know anything about type", gtype
|
||||||
|
else: print 'type:', guess, 'encoding:', encoding
|
|
@ -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 # @UnresolvedImport
|
from python_libtorrent import get_libtorrent
|
||||||
|
lt=get_libtorrent()
|
||||||
lt = get_libtorrent()
|
print('Imported libtorrent v%s from python_libtorrent' %(lt.version, ))
|
||||||
print(('Imported libtorrent v%s from python_libtorrent' % (lt.version,)))
|
except Exception, e:
|
||||||
except Exception as e:
|
print('Error importing python_libtorrent.Exception: %s' %(str(e),))
|
||||||
print(('Error importing python_libtorrent.Exception: %s' % (str(e),)))
|
|
||||||
try:
|
try:
|
||||||
import libtorrent as lt # @UnresolvedImport
|
import libtorrent as lt
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
strerror = e.args
|
strerror = e.args
|
||||||
print(strerror)
|
print(strerror)
|
||||||
|
@ -18,39 +18,28 @@ except Exception as e:
|
||||||
|
|
||||||
from random import SystemRandom
|
from random import SystemRandom
|
||||||
import time
|
import time
|
||||||
import urllib.request, urllib.parse, urllib.error
|
import urllib
|
||||||
import http.server
|
import BaseHTTPServer
|
||||||
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 = 0x02
|
TH32CS_SNAPPROCESS = 0x02L
|
||||||
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot # @UndefinedVariable
|
CreateToolhelp32Snapshot = ctypes.windll.kernel32.CreateToolhelp32Snapshot
|
||||||
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId # @UndefinedVariable
|
GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId
|
||||||
|
|
||||||
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),
|
||||||
|
@ -66,11 +55,9 @@ 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.
|
||||||
|
@ -96,21 +83,18 @@ 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):
|
||||||
|
@ -121,7 +105,6 @@ class Ticker(object):
|
||||||
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
|
||||||
|
@ -144,7 +127,6 @@ class Ticker(object):
|
||||||
self._timer.cancel()
|
self._timer.cancel()
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
|
|
||||||
#######################################################################################
|
#######################################################################################
|
||||||
|
|
||||||
class TorrentFile(object):
|
class TorrentFile(object):
|
||||||
|
@ -157,12 +139,11 @@ 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 = isinstance(self.name, str) and self.name or self.name.decode(chardet.detect(self.name)['encoding'])
|
self.unicode_name = 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
|
||||||
|
@ -174,10 +155,8 @@ 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
|
||||||
|
@ -186,49 +165,34 @@ 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)
|
||||||
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')
|
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("[%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'):
|
return self.filePtr.seek(0, io.SEEK_CUR)
|
||||||
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(
|
not self.pieces_deadlined[(next_piece + i) - self.startPiece] and not self.havePiece(next_piece + i)):
|
||||||
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)
|
||||||
|
@ -237,22 +201,17 @@ 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:
|
||||||
if os.getenv('ANDROID_ROOT'):
|
|
||||||
libc.close(self.filePtr)
|
|
||||||
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_ = ''
|
||||||
|
@ -262,7 +221,6 @@ 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:
|
||||||
|
@ -276,29 +234,20 @@ class TorrentFile(object):
|
||||||
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
|
||||||
if os.getenv('ANDROID_ROOT'):
|
|
||||||
read = libc.read(self.filePtr, addressof(buf), toRead)
|
|
||||||
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
|
||||||
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
|
||||||
if os.getenv('ANDROID_ROOT'):
|
|
||||||
newOffset = libc.lseek64(self.filePtr, offset, whence)
|
|
||||||
else:
|
|
||||||
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):
|
||||||
|
@ -312,25 +261,25 @@ class TorrentFS(object):
|
||||||
progresses = list()
|
progresses = list()
|
||||||
save_path = None
|
save_path = None
|
||||||
|
|
||||||
def __init__(self, root, handle):
|
def __init__(self, root, handle, startIndex):
|
||||||
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())
|
||||||
self.files = {}
|
file_ = self.__file_at_(startIndex)
|
||||||
num_files = self.info.num_files()
|
self.files = {file_.name: file_}
|
||||||
for i in range(num_files):
|
#self.handle.set_piece_deadline(self.files[startIndex].startPiece, 50)
|
||||||
self.setPriority(i, 0)
|
if startIndex < 0:
|
||||||
|
logging.info('No -file-index specified, downloading will be paused until any file is requested')
|
||||||
|
|
||||||
def file(self, index):
|
num_files = self.info.num_files()
|
||||||
for name in list(self.files.keys()):
|
|
||||||
if self.files[name].index == index:
|
for i in range(num_files):
|
||||||
return self.files[name]
|
if startIndex == i:
|
||||||
file_ = self.__file_at_(index)
|
self.setPriority(i, 1)
|
||||||
self.files[file_.name] = file_
|
else:
|
||||||
self.setPriority(index, 1)
|
self.setPriority(i, 0)
|
||||||
return file_
|
|
||||||
|
|
||||||
def Shutdown(self):
|
def Shutdown(self):
|
||||||
self.shuttingDown = True
|
self.shuttingDown = True
|
||||||
|
@ -338,27 +287,22 @@ 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)
|
||||||
|
@ -366,24 +310,19 @@ 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 list(self.files.keys()):
|
for k in 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(
|
if self.files[k].size > 0: self.files[k].progress = float(self.files[k].downloaded) / float(self.files[k].size)
|
||||||
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_ = []
|
||||||
|
@ -391,10 +330,9 @@ 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)
|
||||||
|
@ -406,18 +344,15 @@ 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:
|
||||||
|
@ -429,7 +364,6 @@ 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)
|
||||||
|
@ -447,68 +381,52 @@ class TorrentFS(object):
|
||||||
self.checkPriorities()
|
self.checkPriorities()
|
||||||
return tf
|
return tf
|
||||||
|
|
||||||
|
|
||||||
#############################################################
|
#############################################################
|
||||||
|
|
||||||
class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
|
class ThreadingHTTPServer(SocketServer.ThreadingMixIn, BaseHTTPServer.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:
|
||||||
http.server.HTTPServer.handle_error(self, *args, **kwargs)
|
BaseHTTPServer.HTTPServer.handle_error(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def HttpHandlerFactory():
|
def HttpHandlerFactory():
|
||||||
class HttpHandler(http.server.BaseHTTPRequestHandler):
|
class HttpHandler(BaseHTTPServer.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
|
||||||
if os.getenv('ANDROID_ROOT'):
|
|
||||||
buf = create_string_buffer(chunk)
|
|
||||||
else:
|
|
||||||
buf = bytearray(chunk)
|
buf = bytearray(chunk)
|
||||||
while chunk > 0 and not self.server.root_obj.forceShutdown:
|
while chunk > 0:
|
||||||
if start_range + chunk > end_range:
|
if start_range + chunk > end_range:
|
||||||
chunk = end_range - start_range
|
chunk = end_range - start_range
|
||||||
if os.getenv('ANDROID_ROOT'):
|
|
||||||
buf = create_string_buffer(chunk)
|
|
||||||
else:
|
|
||||||
buf = bytearray(chunk)
|
buf = bytearray(chunk)
|
||||||
try:
|
try:
|
||||||
if f.Read(buf) < 1: break
|
if f.Read(buf) < 1: break
|
||||||
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)
|
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.parse.unquote(self.path.lstrip('/files/'))
|
fname = urllib.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 list(VIDEO_EXTS.keys())) and VIDEO_EXTS[ext] or 'application/octet-stream'
|
ctype = (ext != '' and ext in 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:
|
||||||
|
@ -535,32 +453,27 @@ 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, fmt, *args):
|
def log_message(self, format, *args):
|
||||||
return
|
return
|
||||||
|
|
||||||
return HttpHandler
|
return HttpHandler
|
||||||
|
|
||||||
|
|
||||||
class Pyrrent2http(object):
|
class Pyrrent2http(object):
|
||||||
pause = False
|
def __init__(self, uri = '', bindAddress = 'localhost:5001', downloadPath = '.',
|
||||||
|
idleTimeout = -1, fileIndex = -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):
|
||||||
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
|
||||||
|
@ -571,6 +484,7 @@ 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
|
||||||
|
@ -605,7 +519,6 @@ 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:'):
|
||||||
|
@ -658,8 +571,8 @@ 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(tracker, startTier + n)
|
||||||
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()
|
||||||
|
@ -669,24 +582,30 @@ 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.TorrentFS = TorrentFS(self, self.torrentHandle, self.config.fileIndex)
|
||||||
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.listener_thread = threading.Thread(target=self.httpListener.serve_forever)
|
#self.httpListener.timeout = 0.5
|
||||||
self.listener_thread.start()
|
#thread = threading.Thread(target = http_server_loop, args = (self.httpListener, self.main_alive))
|
||||||
|
thread = threading.Thread(target = self.httpListener.serve_forever)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def startServices(self):
|
def startServices(self):
|
||||||
if self.config.enableDHT:
|
if self.config.enableDHT:
|
||||||
|
@ -728,20 +647,6 @@ 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 != '':
|
||||||
|
@ -810,32 +715,32 @@ 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': self.torrent_name,
|
'name' : info.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):
|
||||||
def Ls(self, index):
|
retFiles = {'files': []}
|
||||||
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
|
||||||
Url = 'http://' + self.config.bindAddress + '/files/' + urllib.parse.quote(name)
|
for name in files.keys():
|
||||||
|
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,
|
||||||
|
@ -847,8 +752,8 @@ class Pyrrent2http(object):
|
||||||
'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():
|
||||||
|
@ -858,8 +763,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,
|
||||||
|
@ -867,14 +772,41 @@ class Pyrrent2http(object):
|
||||||
}
|
}
|
||||||
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:
|
||||||
|
@ -885,8 +817,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:
|
||||||
|
@ -901,6 +833,8 @@ 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)
|
||||||
|
@ -914,18 +848,16 @@ 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 is None:
|
if alert == 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
|
||||||
|
@ -939,7 +871,6 @@ 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:
|
||||||
|
@ -955,7 +886,6 @@ 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():
|
||||||
|
@ -966,7 +896,6 @@ 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
|
||||||
|
@ -982,13 +911,12 @@ 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()
|
||||||
|
@ -998,5 +926,5 @@ class Pyrrent2http(object):
|
||||||
self.saveSessionState()
|
self.saveSessionState()
|
||||||
self.removeTorrent()
|
self.removeTorrent()
|
||||||
logging.info('Aborting the session')
|
logging.info('Aborting the session')
|
||||||
self.session = None
|
del self.session
|
||||||
logging.info('Bye bye')
|
logging.info('Bye bye')
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
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,41 +1,31 @@
|
||||||
import mimetypes
|
|
||||||
import os
|
|
||||||
import socket
|
|
||||||
import urllib.error
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.parse
|
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
import chardet
|
|
||||||
import sys
|
import sys
|
||||||
import xbmc
|
import socket
|
||||||
|
import chardet
|
||||||
from .structs import MediaType
|
import os
|
||||||
|
from . import MediaType
|
||||||
|
import mimetypes
|
||||||
|
import urlparse, urllib
|
||||||
|
|
||||||
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 = urllib.parse.urlparse(uri)
|
fileUri = urlparse.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.parse.unquote(uriPath))
|
absPath = os.path.abspath(urllib.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:
|
||||||
|
@ -51,31 +41,24 @@ 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, str) and tmpl or tmpl.decode(chardet.detect(tmpl)['encoding'])
|
msg = isinstance(tmpl, unicode) and tmpl or tmpl.decode(chardet.detect(tmpl)['encoding'])
|
||||||
arg_ = []
|
arg_ = []
|
||||||
for a in args:
|
for a in args:
|
||||||
arg_.append(isinstance(a, str) and a or a.decode(chardet.detect(a)['encoding']))
|
arg_.append(isinstance(a, unicode) 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, str) and msg.encode(
|
msg = isinstance(msg, unicode) and msg.encode(True and sys.getfilesystemencoding() or 'utf-8') or msg
|
||||||
sys.getfilesystemencoding() != 'ascii' and sys.getfilesystemencoding() or 'utf-8') or msg
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def localize_path(path):
|
def localize_path(path):
|
||||||
if isinstance(path, bytes):
|
if not isinstance(path, unicode): path = path.decode(chardet.detect(path)['encoding'])
|
||||||
path = path.decode(chardet.detect(path)['encoding'])
|
if not sys.platform.startswith('win'):
|
||||||
# if not sys.platform.startswith('win'):
|
path = path.encode(True and sys.getfilesystemencoding() or 'utf-8')
|
||||||
# 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
|
||||||
|
@ -111,6 +94,6 @@ def find_free_port(host):
|
||||||
|
|
||||||
|
|
||||||
def ensure_fs_encoding(string):
|
def ensure_fs_encoding(string):
|
||||||
if isinstance(string, bytes):
|
if isinstance(string, str):
|
||||||
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