folder reorganization

This commit is contained in:
cttynul
2019-04-23 14:32:53 +02:00
parent 659751b2f4
commit 8e7ee78a87
1195 changed files with 267003 additions and 2 deletions
+5
View File
@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from client import Client
__all__ = ["Client"]
+87
View File
@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------
# Gestiona el cache del servidor torrent:
# Guarda los .torrent generado
# Guarda los .resume de cada torrent
# ------------------------------------------------------------
import base64
import os.path
import re
try:
from python_libtorrent import get_libtorrent
lt = get_libtorrent()
except Exception, e:
import libtorrent as lt
class Cache(object):
CACHE_DIR = '.cache'
def __init__(self, path):
if not os.path.isdir(path):
os.makedirs(path)
self.path = os.path.join(path, Cache.CACHE_DIR)
if not os.path.isdir(self.path):
os.makedirs(self.path)
def _tname(self, info_hash):
return os.path.join(self.path, info_hash.upper() + '.torrent')
def _rname(self, info_hash):
return os.path.join(self.path, info_hash.upper() + '.resume')
def save_resume(self, info_hash, data):
f = open(self._rname(info_hash), 'wb')
f.write(data)
f.close()
def get_resume(self, url=None, info_hash=None):
if url:
info_hash = self._index.get(url)
if not info_hash:
return
rname = self._rname(info_hash)
if os.access(rname, os.R_OK):
f = open(rname, 'rb')
v = f.read()
f.close()
return v
def file_complete(self, torrent):
info_hash = str(torrent.info_hash())
nt = lt.create_torrent(torrent)
tname = self._tname(info_hash)
f = open(tname, 'wb')
f.write(lt.bencode(nt.generate()))
f.close()
def get_torrent(self, url=None, info_hash=None):
if url:
info_hash = self._index.get(url)
if not info_hash:
return
tname = self._tname(info_hash)
if os.access(tname, os.R_OK):
return tname
magnet_re = re.compile('xt=urn:btih:([0-9A-Za-z]+)')
hexa_chars = re.compile('^[0-9A-F]+$')
@staticmethod
def hash_from_magnet(m):
res = Cache.magnet_re.search(m)
if res:
ih = res.group(1).upper()
if len(ih) == 40 and Cache.hexa_chars.match(ih):
return res.group(1).upper()
elif len(ih) == 32:
s = base64.b32decode(ih)
return "".join("{:02X}".format(ord(c)) for c in s)
else:
raise ValueError('Not BT magnet link')
else:
raise ValueError('Not BT magnet link')
+555
View File
@@ -0,0 +1,555 @@
# -*- coding: utf-8 -*-
try:
from python_libtorrent import get_libtorrent, get_platform
lt = get_libtorrent()
except Exception, e:
import libtorrent as lt
import os
import pickle
import random
import time
import urllib
from cache import Cache
from dispatcher import Dispatcher
from file import File
from handler import Handler
from monitor import Monitor
from platformcode import logger
from resume_data import ResumeData
from server import Server
class Client(object):
INITIAL_TRACKERS = ['udp://tracker.openbittorrent.com:80',
'udp://tracker.istole.it:80',
'udp://open.demonii.com:80',
'udp://tracker.coppersurfer.tk:80',
'udp://tracker.leechers-paradise.org:6969',
'udp://exodus.desync.com:6969',
'udp://tracker.publicbt.com:80']
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',
'.ogg': 'video/ogg', '.webm': 'video/webm', '.ts': 'video/mp2t', '.3gp': 'video/3gpp'}
def __init__(self, url=None, port=None, ip=None, auto_shutdown=True, wait_time=20, timeout=5, auto_delete=True,
temp_path=None, is_playing_fnc=None, print_status=False):
# server
if port:
self.port = port
else:
self.port = random.randint(8000, 8099)
if ip:
self.ip = ip
else:
self.ip = "127.0.0.1"
self.server = Server((self.ip, self.port), Handler, client=self)
# Options
if temp_path:
self.temp_path = temp_path
else:
self.temp_path = os.path.join(os.path.dirname(__file__), "tmp")
self.is_playing_fnc = is_playing_fnc
self.timeout = timeout
self.auto_delete = auto_delete
self.wait_time = wait_time
self.auto_shutdown = auto_shutdown
self.buffer_size = 15
self.last_pieces_priorize = 5
self.state_file = "state"
self.torrent_paramss = {'save_path': self.temp_path, 'storage_mode': lt.storage_mode_t.storage_mode_sparse}
# State
self.has_meta = False
self.meta = None
self.start_time = None
self.last_connect = 0
self.connected = False
self.closed = False
self.file = None
self.files = None
self._th = None
# Sesion
self._cache = Cache(self.temp_path)
self._ses = lt.session()
self._ses.listen_on(0, 0)
# Cargamos el archivo de estado (si esxiste)
if os.path.exists(os.path.join(self.temp_path, self.state_file)):
try:
f = open(os.path.join(self.temp_path, self.state_file), "rb")
state = pickle.load(f)
self._ses.load_state(state)
f.close()
except:
pass
self._start_services()
# Monitor & Dispatcher
self._monitor = Monitor(self)
if print_status:
self._monitor.add_listener(self.print_status)
self._monitor.add_listener(self._check_meta)
self._monitor.add_listener(self.save_state)
self._monitor.add_listener(self.priorize_start_file)
self._monitor.add_listener(self.announce_torrent)
if self.auto_shutdown:
self._monitor.add_listener(self._auto_shutdown)
self._dispatcher = Dispatcher(self)
self._dispatcher.add_listener(self._update_ready_pieces)
# Iniciamos la URL
if url:
self.start_url(url)
def get_play_list(self):
"""
Función encargada de generar el playlist
"""
# Esperamos a lo metadatos
while not self.has_meta:
time.sleep(1)
# Comprobamos que haya archivos de video
if self.files:
if len(self.files) > 1:
return "http://" + self.ip + ":" + str(self.port) + "/playlist.pls"
else:
return "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(self.files[0].path)
def get_files(self):
"""
Función encargada de genera el listado de archivos
"""
# Esperamos a lo metadatos
while not self.has_meta:
time.sleep(1)
files = []
# Comprobamos que haya archivos de video
if self.files:
# Creamos el dict con los archivos
for file in self.files:
n = file.path
u = "http://" + self.ip + ":" + str(self.port) + "/" + urllib.quote(n)
s = file.size
files.append({"name": n, "url": u, "size": s})
return files
def _find_files(self, files, search=None):
"""
Función encargada de buscar los archivos reproducibles del torrent
"""
# Obtenemos los archivos que la extension este en la lista
videos = filter(lambda f: self.VIDEO_EXTS.has_key(os.path.splitext(f.path)[1]), files)
if not videos:
raise Exception('No video files in torrent')
for v in videos:
videos[videos.index(v)].index = files.index(v)
return videos
def set_file(self, f):
"""
Función encargada de seleccionar el archivo que vamos a servir y por tanto, priorizar su descarga
"""
# Seleccionamos el archivo que vamos a servir
fmap = self.meta.map_file(f.index, 0, 1)
self.file = File(f.path, self.temp_path, f.index, f.size, fmap, self.meta.piece_length(), self)
self.prioritize_file()
def prioritize_piece(self, pc, idx):
"""
Función encargada de priorizar una determinada pieza
"""
piece_duration = 1000
min_deadline = 2000
dl = idx * piece_duration + min_deadline
self._th.set_piece_deadline(pc, dl, lt.deadline_flags.alert_when_available)
if idx == 0:
tail_pieces = 9
# Piezas anteriores a la primera se desactivan
if (self.file.last_piece - pc) > tail_pieces:
for i in xrange(self.file.first_piece, pc):
self._th.piece_priority(i, 0)
self._th.reset_piece_deadline(i)
# Piezas siguientes a la primera se activan
for i in xrange(pc + 1, self.file.last_piece + 1):
self._th.piece_priority(i, 1)
def prioritize_file(self):
"""
Función encargada de priorizar las piezas correspondientes al archivo seleccionado en la funcion set_file()
"""
priorities = []
for i in xrange(self.meta.num_pieces()):
if i >= self.file.first_piece and i <= self.file.last_piece:
priorities.append(1)
else:
priorities.append(0)
self._th.prioritize_pieces(priorities)
def download_torrent(self, url):
"""
Función encargada de descargar un archivo .torrent
"""
from core import httptools
data = httptools.downloadpage(url).data
return data
def start_url(self, uri):
"""
Función encargada iniciar la descarga del torrent desde la url, permite:
- Url apuntando a un .torrent
- Url magnet
- Archivo .torrent local
"""
if self._th:
raise Exception('Torrent is already started')
if uri.startswith('http://') or uri.startswith('https://'):
torrent_data = self.download_torrent(uri)
info = lt.torrent_info(lt.bdecode(torrent_data))
tp = {'ti': info}
resume_data = self._cache.get_resume(info_hash=str(info.info_hash()))
if resume_data:
tp['resume_data'] = resume_data
elif uri.startswith('magnet:'):
tp = {'url': uri}
resume_data = self._cache.get_resume(info_hash=Cache.hash_from_magnet(uri))
if resume_data:
tp['resume_data'] = resume_data
elif os.path.isfile(uri):
if os.access(uri, os.R_OK):
info = lt.torrent_info(uri)
tp = {'ti': info}
resume_data = self._cache.get_resume(info_hash=str(info.info_hash()))
if resume_data:
tp['resume_data'] = resume_data
else:
raise ValueError('Invalid torrent path %s' % uri)
else:
raise ValueError("Invalid torrent %s" % uri)
tp.update(self.torrent_paramss)
self._th = self._ses.add_torrent(tp)
for tr in self.INITIAL_TRACKERS:
self._th.add_tracker({'url': tr})
self._th.set_sequential_download(True)
self._th.force_reannounce()
self._th.force_dht_announce()
self._monitor.start()
self._dispatcher.do_start(self._th, self._ses)
self.server.run()
def stop(self):
"""
Función encargada de de detener el torrent y salir
"""
self._dispatcher.stop()
self._dispatcher.join()
self._monitor.stop()
self.server.stop()
self._dispatcher.stop()
if self._ses:
self._ses.pause()
if self._th:
self.save_resume()
self.save_state()
self._stop_services()
self._ses.remove_torrent(self._th, self.auto_delete)
del self._ses
self.closed = True
def _start_services(self):
"""
Función encargada de iniciar los servicios de libtorrent: dht, lsd, upnp, natpnp
"""
self._ses.add_dht_router("router.bittorrent.com", 6881)
self._ses.add_dht_router("router.bitcomet.com", 554)
self._ses.add_dht_router("router.utorrent.com", 6881)
self._ses.start_dht()
self._ses.start_lsd()
self._ses.start_upnp()
self._ses.start_natpmp()
def _stop_services(self):
"""
Función encargada de detener los servicios de libtorrent: dht, lsd, upnp, natpnp
"""
self._ses.stop_natpmp()
self._ses.stop_upnp()
self._ses.stop_lsd()
self._ses.stop_dht()
def save_resume(self):
"""
Función encargada guardar los metadatos para continuar una descarga mas rapidamente
"""
if self._th.need_save_resume_data() and self._th.is_valid() and self.meta:
r = ResumeData(self)
start = time.time()
while (time.time() - start) <= 5:
if r.data or r.failed:
break
time.sleep(0.1)
if r.data:
self._cache.save_resume(self.unique_file_id, lt.bencode(r.data))
@property
def status(self):
"""
Función encargada de devolver el estado del torrent
"""
if self._th:
s = self._th.status()
# Download Rate
s._download_rate = s.download_rate / 1000
# Progreso del archivo
if self.file:
pieces = s.pieces[self.file.first_piece:self.file.last_piece]
progress = float(sum(pieces)) / len(pieces)
else:
progress = 0
s.progress_file = progress * 100
# Tamaño del archivo
if self.file:
s.file_size = self.file.size / 1048576.0
else:
s.file_size = 0
# Estado del buffer
if self.file and self.file.cursor: # Con una conexion activa: Disponible vs Posicion del reproductor
percent = len(self.file.cursor.cache)
percent = percent * 100 / self.buffer_size
s.buffer = int(percent)
elif self.file: # Sin una conexion activa: Pre-buffer antes de iniciar
# El Pre-buffer consta de dos partes_
# 1. Buffer al inicio del archivo para que el reproductor empieze sin cortes
# 2. Buffer al final del archivo (en algunos archivos el reproductor mira el final del archivo antes de comenzar)
bp = []
# El tamaño del buffer de inicio es el tamaño del buffer menos el tamaño del buffer del final
first_pieces_priorize = self.buffer_size - self.last_pieces_priorize
# Comprobamos que partes del buffer del inicio estan disponibles
for x in range(first_pieces_priorize):
if self._th.have_piece(self.file.first_piece + x):
bp.append(True)
else:
bp.append(False)
# Comprobamos que partes del buffer del final estan disponibles
for x in range(self.last_pieces_priorize):
if self._th.have_piece(self.file.last_piece - x):
bp.append(True)
else:
bp.append(False)
s.buffer = int(sum(bp) * 100 / self.buffer_size)
else: # Si no hay ningun archivo seleccionado: No hay buffer
s.buffer = 0
# Tiempo restante para cerrar en caso de tener el timeout activo
if self.auto_shutdown:
if self.connected:
if self.timeout:
s.timeout = int(self.timeout - (time.time() - self.last_connect - 1))
if self.file and self.file.cursor:
s.timeout = self.timeout
if s.timeout < 0: s.timeout = "Cerrando"
else:
s.timeout = "---"
else:
if self.start_time and self.wait_time:
s.timeout = int(self.wait_time - (time.time() - self.start_time - 1))
if s.timeout < 0: s.timeout = "Cerrando"
else:
s.timeout = "---"
else:
s.timeout = "Off"
# Estado de la descarga
STATE_STR = ['En cola', 'Comprobando', 'Descargando metadata', \
'Descargando', 'Finalizado', 'Seeding', 'Allocating', 'Comprobando fastresume']
s.str_state = STATE_STR[s.state]
# Estado DHT
if self._ses.dht_state() is not None:
s.dht_state = "On"
s.dht_nodes = self._ses.status().dht_nodes
else:
s.dht_state = "Off"
s.dht_nodes = 0
# Cantidad de Trackers
s.trackers = len(self._th.trackers())
# Origen de los peers
s.dht_peers = 0
s.trk_peers = 0
s.pex_peers = 0
s.lsd_peers = 0
for peer in self._th.get_peer_info():
if peer.source & 1:
s.trk_peers += 1
if peer.source & 2:
s.dht_peers += 1
if peer.source & 4:
s.pex_peers += 1
if peer.source & 8:
s.lsd_peers += 1
return s
"""
Servicios:
- Estas funciones se ejecutan de forma automatica cada x tiempo en otro Thread.
- Estas funciones son ejecutadas mientras el torrent esta activo algunas pueden desactivarse
segun la configuracion como por ejemplo la escritura en el log
"""
def _auto_shutdown(self, *args, **kwargs):
"""
Servicio encargado de autoapagar el servidor
"""
if self.file and self.file.cursor:
self.last_connect = time.time()
self.connected = True
if self.is_playing_fnc and self.is_playing_fnc():
self.last_connect = time.time()
self.connected = True
if self.auto_shutdown:
# shudown por haber cerrado el reproductor
if self.connected and self.is_playing_fnc and not self.is_playing_fnc():
if time.time() - self.last_connect - 1 > self.timeout:
self.stop()
# shutdown por no realizar ninguna conexion
if (not self.file or not self.file.cursor) and self.start_time and self.wait_time and not self.connected:
if time.time() - self.start_time - 1 > self.wait_time:
self.stop()
# shutdown tras la ultima conexion
if (not self.file or not self.file.cursor) and self.timeout and self.connected and not self.is_playing_fnc:
if time.time() - self.last_connect - 1 > self.timeout:
self.stop()
def announce_torrent(self):
"""
Servicio encargado de anunciar el torrent
"""
self._th.force_reannounce()
self._th.force_dht_announce()
def save_state(self):
"""
Servicio encargado de guardar el estado
"""
state = self._ses.save_state()
f = open(os.path.join(self.temp_path, self.state_file), 'wb')
pickle.dump(state, f)
f.close()
def _update_ready_pieces(self, alert_type, alert):
"""
Servicio encargado de informar que hay una pieza disponible
"""
if alert_type == 'read_piece_alert' and self.file:
self.file.update_piece(alert.piece, alert.buffer)
def _check_meta(self):
"""
Servicio encargado de comprobar si los metadatos se han descargado
"""
if self.status.state >= 3 and self.status.state <= 5 and not self.has_meta:
# Guardamos los metadatos
self.meta = self._th.get_torrent_info()
# Obtenemos la lista de archivos del meta
fs = self.meta.files()
if isinstance(fs, list):
files = fs
else:
files = [fs.at(i) for i in xrange(fs.num_files())]
# Guardamos la lista de archivos
self.files = self._find_files(files)
# Marcamos el primer archivo como activo
self.set_file(self.files[0])
# Damos por iniciada la descarga
self.start_time = time.time()
# Guardamos el .torrent en el cahce
self._cache.file_complete(self._th.get_torrent_info())
self.has_meta = True
def priorize_start_file(self):
'''
Servicio encargado de priorizar el principio y final de archivo cuando no hay conexion
'''
if self.file and not self.file.cursor:
num_start_pieces = self.buffer_size - self.last_pieces_priorize # Cantidad de piezas a priorizar al inicio
num_end_pieces = self.last_pieces_priorize # Canridad de piezas a priorizar al final
pieces_count = 0
# Priorizamos las ultimas piezas
for y in range(self.file.last_piece - num_end_pieces, self.file.last_piece + 1):
if not self._th.have_piece(y):
self.prioritize_piece(y, pieces_count)
pieces_count += 1
# Priorizamos las primeras piezas
for y in range(self.file.first_piece, self.file.last_piece + 1):
if not self._th.have_piece(y):
if pieces_count == self.buffer_size:
break
self.prioritize_piece(y, pieces_count)
pieces_count += 1
def print_status(self):
'''
Servicio encargado de mostrar en el log el estado de la descarga
'''
s = self.status
if self.file:
archivo = self.file.index
else:
archivo = "N/D"
logger.info(
'%.2f%% de %.1fMB %s | %.1f kB/s | #%s %d%% | AutoClose: %s | S: %d(%d) P: %d(%d)) | TRK: %d DHT: %d PEX: %d LSD %d | DHT:%s (%d) | Trakers: %d' % \
(s.progress_file, s.file_size, s.str_state, s._download_rate, archivo, s.buffer, s.timeout, s.num_seeds, \
s.num_complete, s.num_peers, s.num_incomplete, s.trk_peers, s.dht_peers, s.pex_peers, s.lsd_peers,
s.dht_state, s.dht_nodes, s.trackers))
+106
View File
@@ -0,0 +1,106 @@
# -*- coding: utf-8 -*-
from threading import Lock, Event
class Cursor(object):
def __init__(self, file):
self._file = file
self.pos = 0
self.timeout = 30
self.cache_size = self._file._client.buffer_size
self.cache = {}
self.lock = Lock()
self.event = Event()
self.cache_first = 0
def fill_cache(self, first):
self.cache_first = first
with self.lock:
for p in sorted(self.cache):
if p < first: del self.cache[p]
self.event.clear()
for i in xrange(first, first + self.cache_size):
if i <= self._file.last_piece:
self._file._client.prioritize_piece(i, i - first)
def has_piece(self, n):
with self.lock:
return n in self.cache
def _wait_piece(self, pc_no):
while not self.has_piece(pc_no):
self.fill_cache(pc_no)
self.event.wait(self.timeout)
def _get_piece(self, n):
with self.lock:
if not n in self.cache:
raise ValueError('index of of scope of current cache')
return self.cache[n]
def get_piece(self, n):
self._wait_piece(n)
return self._get_piece(n)
def close(self):
self._file.cursor = None
def read(self, size=None):
data = ""
max_size = self._file.size - self.pos
if not size:
size = max_size
else:
size = min(size, max_size)
if size:
pc_no, ofs = self._file.map_piece(self.pos)
data = self.get_piece(pc_no)[ofs: ofs + size]
if len(data) < size:
remains = size - len(data)
pc_no += 1
self.fill_cache(pc_no)
while remains and self.has_piece(pc_no):
sz = min(remains, self._file.piece_size)
data += self.get_piece(pc_no)[:sz]
remains -= sz
if remains:
pc_no += 1
self.fill_cache(pc_no)
self.pos += len(data)
return data
def seek(self, n):
if n > self._file.size:
n = self._file.size
elif n < 0:
raise ValueError('Seeking negative')
self.pos = n
def tell(self):
return self.pos
def update_piece(self, n, data):
with self.lock:
pcs = sorted(self.cache)
if len(pcs) < self.cache_size:
if len(pcs):
new = max(pcs) + 1
else:
new = self.cache_first
if n == new:
self.cache[n] = data
if n == self.cache_first:
self.event.set()
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
+33
View File
@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
from monitor import Monitor
try:
from python_libtorrent import get_libtorrent
lt = get_libtorrent()
except Exception, e:
import libtorrent as lt
class Dispatcher(Monitor):
def __init__(self, client):
super(Dispatcher, self).__init__(client)
def do_start(self, th, ses):
self._th = th
self._ses = ses
self.start()
def run(self):
if not self._ses:
raise Exception('Invalid state, session is not initialized')
while self.running:
a = self._ses.wait_for_alert(1000)
if a:
alerts = self._ses.pop_alerts()
for alert in alerts:
with self.lock:
for cb in self.listeners:
cb(lt.alert.what(alert), alert)
+39
View File
@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
import os
from cursor import Cursor
class File(object):
def __init__(self, path, base, index, size, fmap, piece_size, client):
self._client = client
self.path = path
self.base = base
self.index = index
self.size = size
self.piece_size = piece_size
self.full_path = os.path.join(base, path)
self.first_piece = fmap.piece
self.offset = fmap.start
self.last_piece = self.first_piece + max((size - 1 + fmap.start), 0) // piece_size
self.cursor = None
def create_cursor(self, offset=None):
self.cursor = Cursor(self)
if offset:
self.cursor.seek(offset)
return self.cursor
def map_piece(self, ofs):
return self.first_piece + (ofs + self.offset) // self.piece_size, (ofs + self.offset) % self.piece_size
def update_piece(self, n, data):
if self.cursor:
self.cursor.update_piece(n, data)
def __str__(self):
return self.path
+135
View File
@@ -0,0 +1,135 @@
# -*- coding: utf-8 -*-
import BaseHTTPServer
import os
import re
import time
import types
import urllib
import urlparse
RANGE_RE = re.compile(r'bytes=(\d+)-')
def parse_range(range): # @ReservedAssignment
if range:
m = RANGE_RE.match(range)
if m:
try:
return int(m.group(1))
except:
pass
return 0
class Handler(BaseHTTPServer.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
def log_message(self, format, *args):
pass
def do_GET(self):
if self.server.request:
self.server.request.wfile.close()
self.server.request = self
if self.do_HEAD():
f = self.server.file.create_cursor(self.offset)
while f == self.server.file.cursor:
buf = f.read(1024)
if buf:
try:
self.wfile.write(buf)
except:
break
else:
break
f.close()
def send_pls(self, files):
playlist = "[playlist]\n\n"
for x, f in enumerate(files):
playlist += "File" + str(x + 1) + "=http://127.0.0.1:" + str(self.server._client.port) + "/" + urllib.quote(
f.path) + "\n"
playlist += "Title" + str(x + 1) + "=" + f.path + "\n"
playlist += "NumberOfEntries=" + str(len(files))
playlist += "Version=2"
self.send_response(200, 'OK')
self.send_header("Content-Length", str(len(playlist)))
self.finish_header()
self.wfile.write(playlist)
def do_HEAD(self):
url = urlparse.urlparse(self.path).path
'''Whait to list of files '''
while not self.server._client.files:
time.sleep(1)
files = self.server._client.files
self.server.file = self.server._client.file
'''Creates PLS playlist '''
if url == "/playlist.pls":
self.send_pls(files)
return False
'''Change File to download '''
if not self.server.file or urllib.unquote(url) != '/' + self.server.file.path:
file = urllib.unquote(url)
client = self.server._client
for f in client.files:
if file == '/' + f.path:
client.set_file(f)
self.server.file = client.file
break
while not self.server._client.has_meta:
time.sleep(1)
if self.server.file and urllib.unquote(url) == '/' + self.server.file.path:
self.offset = 0
size, mime = self._file_info()
range = parse_range(self.headers.get('Range', None))
if range:
self.offset = range
range = (range, size - 1, size)
self.send_resp_header(mime, size, range)
return True
else:
self.send_error(404, 'Not Found')
def _file_info(self):
size = self.server.file.size
ext = os.path.splitext(self.server.file.path)[1]
mime = self.server._client.VIDEO_EXTS.get(ext)
if not mime:
mime = 'application/octet-stream'
return size, mime
def send_resp_header(self, cont_type, cont_length, range=False): # @ReservedAssignment
if range:
self.send_response(206, 'Partial Content')
else:
self.send_response(200, 'OK')
self.send_header('Content-Type', cont_type)
self.send_header('transferMode.dlna.org', 'Streaming')
self.send_header('contentFeatures.dlna.org',
'DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000')
self.send_header('Accept-Ranges', 'bytes')
if range:
if isinstance(range, (types.TupleType, types.ListType)) and len(range) == 3:
self.send_header('Content-Range', 'bytes %d-%d/%d' % range)
self.send_header('Content-Length', range[1] - range[0] + 1)
else:
raise ValueError('Invalid range value')
else:
self.send_header('Content-Length', cont_length)
self.finish_header()
def finish_header(self):
self.send_header('Connection', 'close')
self.end_headers()
+44
View File
@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from threading import Thread, Lock, Event
class Monitor(Thread):
def __init__(self, client):
Thread.__init__(self)
self.daemon = True
self.listeners = []
self.lock = Lock()
self.wait_event = Event()
self.running = True
self.client = client
self.ses = None
self.client = client
def stop(self):
self.running = False
self.wait_event.set()
def add_listener(self, cb):
with self.lock:
if not cb in self.listeners:
self.listeners.append(cb)
def remove_listener(self, cb):
with self.lock:
try:
self.listeners.remove(cb)
except ValueError:
pass
def remove_all_listeners(self):
with self.lock:
self.listeners = []
def run(self):
while (self.running):
with self.lock:
for cb in self.listeners:
cb()
self.wait_event.wait(1.0)
+15
View File
@@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
class ResumeData(object):
def __init__(self, client):
self.data = None
self.failed = False
client._dispatcher.add_listener(self._process_alert)
client._th.save_resume_data()
def _process_alert(self, t, alert):
if t == 'save_resume_data_failed_alert':
self.failed = True
elif t == 'save_resume_data_alert':
self.data = alert.resume_data
+37
View File
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
import BaseHTTPServer
import traceback
from SocketServer import ThreadingMixIn
from threading import Thread
class Server(ThreadingMixIn, BaseHTTPServer.HTTPServer):
daemon_threads = True
timeout = 1
def __init__(self, address, handler, client):
BaseHTTPServer.HTTPServer.__init__(self, address, handler)
self._client = client
self.file = None
self.running = True
self.request = None
def stop(self):
self.running = False
def serve(self):
while self.running:
try:
self.handle_request()
except:
print traceback.format_exc()
def run(self):
t = Thread(target=self.serve, name='HTTP Server')
t.daemon = True
t.start()
def handle_error(self, request, client_address):
if not "socket.py" in traceback.format_exc():
print traceback.format_exc()