better remote control

This commit is contained in:
JohnDoee
2017-08-12 13:27:32 +02:00
parent d0d43b8c66
commit e494dad9bf
6 changed files with 307 additions and 210 deletions

View File

@@ -36,6 +36,9 @@ The _allow remote_ option is to allow remote add and stream of torrents.
# Version Info # Version Info
## Version 0.8.0
* Improved remote control of streaming to make it work as originally intended.
## Version 0.7.1 ## Version 0.7.1
* Trying to fix bug where piece buffer went empty * Trying to fix bug where piece buffer went empty
* Added support for SSL. * Added support for SSL.

View File

@@ -42,7 +42,7 @@ from setuptools import setup
__plugin_name__ = "Streaming" __plugin_name__ = "Streaming"
__author__ = "John Doee" __author__ = "John Doee"
__author_email__ = "johndoee@tidalstream.org" __author_email__ = "johndoee@tidalstream.org"
__version__ = "0.7.1" __version__ = "0.8.0"
__url__ = "https://github.com/JohnDoee/deluge-streaming" __url__ = "https://github.com/JohnDoee/deluge-streaming"
__license__ = "GPLv3" __license__ = "GPLv3"
__description__ = "Enables streaming of files while downloading them." __description__ = "Enables streaming of files while downloading them."

View File

@@ -89,14 +89,14 @@ class ServerContextFactory(object):
def __init__(self, cert_file, key_file): def __init__(self, cert_file, key_file):
self._cert_file = cert_file self._cert_file = cert_file
self._key_file = key_file self._key_file = key_file
def getContext(self): def getContext(self):
from OpenSSL import SSL from OpenSSL import SSL
method = getattr(SSL, 'TLSv1_1_METHOD', None) method = getattr(SSL, 'TLSv1_1_METHOD', None)
if method is None: if method is None:
method = getattr(SSL, 'SSLv23_METHOD', None) method = getattr(SSL, 'SSLv23_METHOD', None)
ctx = SSL.Context(method) ctx = SSL.Context(method)
ctx.use_certificate_file(self._cert_file) ctx.use_certificate_file(self._cert_file)
ctx.use_certificate_chain_file(self._cert_file) ctx.use_certificate_chain_file(self._cert_file)
@@ -105,25 +105,25 @@ class ServerContextFactory(object):
class FileServeResource(resource.Resource): class FileServeResource(resource.Resource):
isLeaf = True isLeaf = True
def __init__(self): def __init__(self):
self.file_mapping = {} self.file_mapping = {}
resource.Resource.__init__(self) resource.Resource.__init__(self)
def generate_secure_token(self): def generate_secure_token(self):
return base64.urlsafe_b64encode(randbytes.RandomFactory().secureRandom(21, True)) return base64.urlsafe_b64encode(randbytes.RandomFactory().secureRandom(21, True))
def add_file(self, path): def add_file(self, path):
token = self.generate_secure_token() token = self.generate_secure_token()
self.file_mapping[token] = path self.file_mapping[token] = path
return token return token
def render_GET(self, request): def render_GET(self, request):
key = request.postpath[0] key = request.postpath[0]
if key not in self.file_mapping: if key not in self.file_mapping:
return resource.NoResource().render(request) return resource.NoResource().render(request)
f = self.file_mapping[key] f = self.file_mapping[key]
if f.is_complete(): if f.is_complete():
return static.File(f.full_path).render_GET(request) return static.File(f.full_path).render_GET(request)
@@ -133,23 +133,51 @@ class FileServeResource(resource.Resource):
class StreamResource(Resource): class StreamResource(Resource):
isLeaf = True isLeaf = True
def __init__(self, client, *args, **kwargs): def __init__(self, client, *args, **kwargs):
self.client = client self.client = client
Resource.__init__(self, *args, **kwargs) Resource.__init__(self, *args, **kwargs)
@defer.inlineCallbacks
def render_POST(self, request):
infohash = request.args.get('infohash')
path = request.args.get('path')
wait_for_end_pieces = bool(request.args.get('wait_for_end_pieces'))
if path:
path = path[0]
else:
path = None
if infohash:
infohash = infohash[0]
else:
infohash = infohash
payload = request.content.read()
if not payload:
defer.returnValue(json.dumps({'status': 'error', 'message': 'invalid torrent'}))
result = yield self.client.stream_torrent(infohash=infohash, filedump=payload, filepath_or_index=path, wait_for_end_pieces=wait_for_end_pieces)
defer.returnValue(json.dumps(result))
@defer.inlineCallbacks @defer.inlineCallbacks
def render_GET(self, request): def render_GET(self, request):
infohash = request.args.get('infohash', None) infohash = request.args.get('infohash')
path = request.args.get('path', None) path = request.args.get('path')
wait_for_end_pieces = bool(request.args.get('wait_for_end_pieces'))
if infohash is None:
if not infohash:
defer.returnValue(json.dumps({'status': 'error', 'message': 'missing infohash'})) defer.returnValue(json.dumps({'status': 'error', 'message': 'missing infohash'}))
if path is None: infohash = infohash[0]
defer.returnValue(json.dumps({'status': 'error', 'message': 'missing path'}))
if path:
result = yield self.client.stream_torrent(infohash[0], path[0]) path = path[0]
else:
path = None
result = yield self.client.stream_torrent(infohash=infohash, filepath_or_index=path, wait_for_end_pieces=wait_for_end_pieces)
defer.returnValue(json.dumps(result)) defer.returnValue(json.dumps(result))
class UnknownTorrentException(Exception): class UnknownTorrentException(Exception):
@@ -163,34 +191,34 @@ class TorrentFileReader(object):
self.torrent_file = torrent_file self.torrent_file = torrent_file
self.size = torrent_file.size self.size = torrent_file.size
self.position = 0 self.position = 0
self.waiting_for_piece = None self.waiting_for_piece = None
self.current_piece = None self.current_piece = None
self.current_piece_data = None self.current_piece_data = None
@defer.inlineCallbacks @defer.inlineCallbacks
def read(self, size=1024): def read(self, size=1024):
required_piece, read_position = self.torrent_file.get_piece_info(self.position) required_piece, read_position = self.torrent_file.get_piece_info(self.position)
if self.current_piece != required_piece: if self.current_piece != required_piece:
logger.debug('We are missing piece %i and it is required, requesting' % (required_piece, )) logger.debug('We are missing piece %i and it is required, requesting' % (required_piece, ))
self.waiting_for_piece = required_piece self.waiting_for_piece = required_piece
self.current_piece_data = yield self.torrent_file.get_piece_data(required_piece) self.current_piece_data = yield self.torrent_file.get_piece_data(required_piece)
self.current_piece = required_piece self.current_piece = required_piece
self.waiting_for_piece = None self.waiting_for_piece = None
logger.debug('We can read from local piece from %s size %s from position %s' % (read_position, size, self.position)) logger.debug('We can read from local piece from %s size %s from position %s' % (read_position, size, self.position))
data = self.current_piece_data[read_position:read_position+size] data = self.current_piece_data[read_position:read_position+size]
self.position += len(data) self.position += len(data)
defer.returnValue(data) defer.returnValue(data)
def tell(self): def tell(self):
return self.position return self.position
def close(self): def close(self):
self.torrent_file.close(self) self.torrent_file.close(self)
def seek(self, offset, whence=os.SEEK_SET): def seek(self, offset, whence=os.SEEK_SET):
self.position = offset self.position = offset
@@ -205,7 +233,7 @@ class TorrentFile(object): # can be read from, knows about itself
self.size = size self.size = size
self.full_path = full_path self.full_path = full_path
self.index = index self.index = index
self.file_requested = False self.file_requested = False
self.file_requested_once = False self.file_requested_once = False
self.do_shutdown = False self.do_shutdown = False
@@ -213,10 +241,10 @@ class TorrentFile(object): # can be read from, knows about itself
self.waiting_pieces = {} self.waiting_pieces = {}
self.current_readers = [] self.current_readers = []
self.registered_alert = False self.registered_alert = False
self.alerts = component.get("AlertManager") self.alerts = component.get("AlertManager")
def open(self): def open(self):
""" """
Returns a filelike object Returns a filelike object
@@ -224,78 +252,78 @@ class TorrentFile(object): # can be read from, knows about itself
if not self.registered_alert: if not self.registered_alert:
self.alerts.register_handler("read_piece_alert", self.on_alert_got_piece_data) self.alerts.register_handler("read_piece_alert", self.on_alert_got_piece_data)
self.registered_alert = True self.registered_alert = True
tfr = TorrentFileReader(self) tfr = TorrentFileReader(self)
self.current_readers.append(tfr) self.current_readers.append(tfr)
self.file_requested = False self.file_requested = False
return tfr return tfr
def close(self, tfr): def close(self, tfr):
self.current_readers.remove(tfr) self.current_readers.remove(tfr)
def is_complete(self): def is_complete(self):
torrent_status = self.torrent.torrent.get_status(['file_progress', 'state']) torrent_status = self.torrent.torrent.get_status(['file_progress', 'state'])
file_progress = torrent_status['file_progress'] file_progress = torrent_status['file_progress']
return file_progress[self.index] == 1.0 return file_progress[self.index] == 1.0
def get_piece_info(self, tell): def get_piece_info(self, tell):
return divmod((self.offset + tell), self.piece_size) return divmod((self.offset + tell), self.piece_size)
def on_alert_got_piece_data(self, alert): def on_alert_got_piece_data(self, alert):
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
if torrent_id != self.torrent.infohash: if torrent_id != self.torrent.infohash:
return return
logger.debug('Got piece data for piece %s' % alert.piece) logger.debug('Got piece data for piece %s' % alert.piece)
if alert.piece not in self.waiting_pieces: if alert.piece not in self.waiting_pieces:
logger.debug('Got data for piece %i, but no data needed for this piece?' % alert.piece) logger.debug('Got data for piece %i, but no data needed for this piece?' % alert.piece)
return return
if alert.buffer is None: if alert.buffer is None:
return return
piece_data = copy(alert.buffer) piece_data = copy(alert.buffer)
self.waiting_pieces[alert.piece].callback(piece_data) self.waiting_pieces[alert.piece].callback(piece_data)
@defer.inlineCallbacks @defer.inlineCallbacks
def wait_for_end_pieces(self): def wait_for_end_pieces(self):
handle = self.torrent.torrent.handle handle = self.torrent.torrent.handle
for piece in [self.first_piece, self.last_piece]: for piece in [self.first_piece, self.last_piece]:
handle.set_piece_deadline(piece, 0) handle.set_piece_deadline(piece, 0)
handle.piece_priority(piece, 7) handle.piece_priority(piece, 7)
while not handle.have_piece(self.first_piece) and not handle.have_piece(self.last_piece): while not handle.have_piece(self.first_piece) and not handle.have_piece(self.last_piece):
if self.do_shutdown: if self.do_shutdown:
raise Exception('Shutting down') raise Exception('Shutting down')
logger.debug('Did not have piece %i, waiting' % piece) logger.debug('Did not have piece %i, waiting' % piece)
yield sleep(1) yield sleep(1)
@defer.inlineCallbacks @defer.inlineCallbacks
def get_piece_data(self, piece): def get_piece_data(self, piece):
logger.debug('Trying to get piece data for piece %s' % piece) logger.debug('Trying to get piece data for piece %s' % piece)
for reader in self.current_readers: for reader in self.current_readers:
if reader.current_piece == piece: if reader.current_piece == piece:
defer.returnValue(reader.current_piece_data) defer.returnValue(reader.current_piece_data)
if piece not in self.waiting_pieces: if piece not in self.waiting_pieces:
self.waiting_pieces[piece] = defer.Deferred() self.waiting_pieces[piece] = defer.Deferred()
logger.debug('Waiting for %s' % piece) logger.debug('Waiting for %s' % piece)
while not self.torrent.torrent.handle.have_piece(piece): while not self.torrent.torrent.handle.have_piece(piece):
if self.do_shutdown: if self.do_shutdown:
raise Exception('Shutting down') raise Exception('Shutting down')
logger.debug('Did not have piece %i, waiting' % piece) logger.debug('Did not have piece %i, waiting' % piece)
yield sleep(1) yield sleep(1)
self.torrent.torrent.handle.read_piece(piece) self.torrent.torrent.handle.read_piece(piece)
data = yield self.waiting_pieces[piece] data = yield self.waiting_pieces[piece]
if piece in self.waiting_pieces: if piece in self.waiting_pieces:
del self.waiting_pieces[piece] del self.waiting_pieces[piece]
logger.debug('Done waiting for piece %i, returning data' % piece) logger.debug('Done waiting for piece %i, returning data' % piece)
defer.returnValue(data) defer.returnValue(data)
def shutdown(self): def shutdown(self):
self.do_shutdown = True self.do_shutdown = True
@@ -304,51 +332,51 @@ class Torrent(object):
self.infohash = infohash self.infohash = infohash
self.torrent = component.get("TorrentManager").torrents.get(infohash, None) self.torrent = component.get("TorrentManager").torrents.get(infohash, None)
self.torrent_handler = torrent_handler self.torrent_handler = torrent_handler
if not self.torrent: if not self.torrent:
raise UnknownTorrentException('%s is not a known infohash' % infohash) raise UnknownTorrentException('%s is not a known infohash' % infohash)
self.torrent_files = None self.torrent_files = None
self.priority_increased = defaultdict(set) self.priority_increased = defaultdict(set)
self.do_shutdown = False self.do_shutdown = False
self.torrent_released = True # set to True if all the files are set to download self.torrent_released = True # set to True if all the files are set to download
self.populate_files() self.populate_files()
self.file_priorities = [0] * len(self.torrent_files) self.file_priorities = [0] * len(self.torrent_files)
self.last_piece = self.torrent_files[-1].last_piece self.last_piece = self.torrent_files[-1].last_piece
self.torrent.handle.set_sequential_download(True) self.torrent.handle.set_sequential_download(True)
self.torrent.handle.set_priority(1) self.torrent.handle.set_priority(1)
reactor.callLater(0, self.update_piece_priority) reactor.callLater(0, self.update_piece_priority)
def populate_files(self): def populate_files(self):
self.torrent_files = [] self.torrent_files = []
status = self.torrent.get_status(['piece_length', 'files', 'save_path']) status = self.torrent.get_status(['piece_length', 'files', 'save_path'])
piece_length = status['piece_length'] piece_length = status['piece_length']
files = status['files'] files = status['files']
save_path = status['save_path'] save_path = status['save_path']
for f in files: for f in files:
first_piece = f['offset'] / piece_length first_piece = f['offset'] / piece_length
last_piece = (f['offset'] + f['size']) / piece_length last_piece = (f['offset'] + f['size']) / piece_length
full_path = os.path.join(save_path, f['path']) full_path = os.path.join(save_path, f['path'])
self.torrent_files.append(TorrentFile(self, first_piece, last_piece, piece_length, f['offset'], self.torrent_files.append(TorrentFile(self, first_piece, last_piece, piece_length, f['offset'],
f['path'], full_path, f['size'], f['index'])) f['path'], full_path, f['size'], f['index']))
return files return files
def find_file(self, file_or_index=None, includes_name=False): def find_file(self, file_or_index=None, includes_name=False):
best_file = None best_file = None
biggest_file_size = 0 biggest_file_size = 0
for i, f in enumerate(self.torrent_files): for i, f in enumerate(self.torrent_files):
path = f.path path = f.path
if not includes_name and '/' in path: if not includes_name and '/' in path:
path = '/'.join(path.split('/')[1:]) path = '/'.join(path.split('/')[1:])
logger.debug('Testing file %r against %s / %r' % (file_or_index, i, path)) logger.debug('Testing file %r against %s / %r' % (file_or_index, i, path))
if file_or_index is not None: if file_or_index is not None:
if i == file_or_index or path == file_or_index: if i == file_or_index or path == file_or_index:
@@ -358,115 +386,115 @@ class Torrent(object):
if f.size > biggest_file_size: if f.size > biggest_file_size:
best_file = f best_file = f
biggest_file_size = f.size biggest_file_size = f.size
return best_file return best_file
def get_file(self, file_or_index=None, includes_name=False): def get_file(self, file_or_index=None, includes_name=False):
f = self.find_file(file_or_index, includes_name) f = self.find_file(file_or_index, includes_name)
if f is None: if f is None:
raise UnknownFileException('Was unable to find %s' % file_or_index) raise UnknownFileException('Was unable to find %s' % file_or_index)
return f return f
def get_currently_downloading(self): def get_currently_downloading(self):
currently_downloading = set() currently_downloading = set()
for peer in self.torrent.handle.get_peer_info(): for peer in self.torrent.handle.get_peer_info():
if peer.downloading_piece_index != -1: if peer.downloading_piece_index != -1:
currently_downloading.add(peer.downloading_piece_index) currently_downloading.add(peer.downloading_piece_index)
return currently_downloading return currently_downloading
def get_torrent_file(self, file_or_index, includes_name): def get_torrent_file(self, file_or_index, includes_name):
f = self.get_file(file_or_index, includes_name) f = self.get_file(file_or_index, includes_name)
f.file_requested = True f.file_requested = True
f.file_requested_once = True f.file_requested_once = True
self.torrent.resume() self.torrent.resume()
should_update_priorities = False should_update_priorities = False
if self.file_priorities[f.index] == 0: if self.file_priorities[f.index] == 0:
self.file_priorities[f.index] = 3 self.file_priorities[f.index] = 3
should_update_priorities = True should_update_priorities = True
if self.torrent_released: if self.torrent_released:
should_update_priorities = True should_update_priorities = True
if should_update_priorities and not f.is_complete(): # Need to do this stuff on seek too if should_update_priorities and not f.is_complete(): # Need to do this stuff on seek too
self.torrent.set_file_priorities(self.file_priorities) self.torrent.set_file_priorities(self.file_priorities)
return f return f
def shutdown(self): def shutdown(self):
logger.info('Shutting down torrent %s' % (self.infohash, )) logger.info('Shutting down torrent %s' % (self.infohash, ))
self.torrent.handle.set_priority(0) self.torrent.handle.set_priority(0)
for piece, status in enumerate(self.torrent.status.pieces[0:self.last_piece+1]): for piece, status in enumerate(self.torrent.status.pieces[0:self.last_piece+1]):
if status: if status:
continue continue
priority = self.torrent.handle.piece_priority(piece) priority = self.torrent.handle.piece_priority(piece)
if priority == 0: if priority == 0:
self.torrent.handle.piece_priority(piece, 1) self.torrent.handle.piece_priority(piece, 1)
if not self.torrent_handler.config['download_only_streamed']: if not self.torrent_handler.config['download_only_streamed']:
logger.debug('Resetting file priorities') logger.debug('Resetting file priorities')
file_priorities = [(1 if fp == 0 else fp) for fp in self.file_priorities] file_priorities = [(1 if fp == 0 else fp) for fp in self.file_priorities]
self.torrent.set_file_priorities(file_priorities) self.torrent.set_file_priorities(file_priorities)
self.do_shutdown = True self.do_shutdown = True
self.torrent_handler.remove_torrent(self.infohash) self.torrent_handler.remove_torrent(self.infohash)
for tf in self.torrent_files: for tf in self.torrent_files:
tf.shutdown() tf.shutdown()
def update_piece_priority(self): # if file streamed has reached end, unblacklist all prior pieces def update_piece_priority(self): # if file streamed has reached end, unblacklist all prior pieces
if self.do_shutdown: if self.do_shutdown:
return return
logger.debug('Updating piece priority for %s' % (self.infohash, )) logger.debug('Updating piece priority for %s' % (self.infohash, ))
currently_downloading = self.get_currently_downloading() currently_downloading = self.get_currently_downloading()
for f in self.torrent_files: for f in self.torrent_files:
if not f.file_requested and not f.current_readers: # nobody wants the file and nobody is watching if not f.file_requested and not f.current_readers: # nobody wants the file and nobody is watching
continue continue
logger.debug('Rescheduling file %s' % (f.path, )) logger.debug('Rescheduling file %s' % (f.path, ))
heads = set() heads = set()
if f.file_requested: # we expect a piece head to be at start if f.file_requested: # we expect a piece head to be at start
heads.add(f.first_piece) heads.add(f.first_piece)
waiting_for_pieces = set() waiting_for_pieces = set()
for tfr in f.current_readers: for tfr in f.current_readers:
if tfr.waiting_for_piece is not None: if tfr.waiting_for_piece is not None:
waiting_for_pieces.add(tfr.waiting_for_piece) waiting_for_pieces.add(tfr.waiting_for_piece)
piece = max(tfr.waiting_for_piece, tfr.current_piece) piece = max(tfr.waiting_for_piece, tfr.current_piece)
if piece is not None: if piece is not None:
heads.add(piece) heads.add(piece)
if not heads: if not heads:
continue continue
first_head = min(heads) first_head = min(heads)
for head_piece in heads: for head_piece in heads:
priority_increased = 0 priority_increased = 0
for piece, status in enumerate(self.torrent.status.pieces[head_piece:f.last_piece+1], head_piece): for piece, status in enumerate(self.torrent.status.pieces[head_piece:f.last_piece+1], head_piece):
#logger.debug('Checking status for %s/%s/%s/%s' % (head_piece, piece, status, self.torrent.handle.piece_priority(piece))) #logger.debug('Checking status for %s/%s/%s/%s' % (head_piece, piece, status, self.torrent.handle.piece_priority(piece)))
if status or piece in currently_downloading: if status or piece in currently_downloading:
continue continue
priority = self.torrent.handle.piece_priority(piece) priority = self.torrent.handle.piece_priority(piece)
if priority_increased < PRIORITY_INCREASE: if priority_increased < PRIORITY_INCREASE:
priority_increased += 1 priority_increased += 1
if piece in waiting_for_pieces: if piece in waiting_for_pieces:
if priority < 7: if priority < 7:
logger.debug('setting priority for %s to 7 with deadline 0' % (piece, )) logger.debug('setting priority for %s to 7 with deadline 0' % (piece, ))
self.torrent.handle.set_piece_deadline(piece, 0) self.torrent.handle.set_piece_deadline(piece, 0)
self.torrent.handle.piece_priority(piece, 7) self.torrent.handle.piece_priority(piece, 7)
elif priority < 6: elif priority < 6:
@@ -474,17 +502,17 @@ class Torrent(object):
logger.debug('setting priority for %s to 6 with deadline %s' % (piece, deadline, )) logger.debug('setting priority for %s to 6 with deadline %s' % (piece, deadline, ))
self.torrent.handle.piece_priority(piece, 6) self.torrent.handle.piece_priority(piece, 6)
self.torrent.handle.set_piece_deadline(piece, deadline) self.torrent.handle.set_piece_deadline(piece, deadline)
elif priority == 0: elif priority == 0:
self.torrent.handle.piece_priority(piece, 1) self.torrent.handle.piece_priority(piece, 1)
if head_piece == first_head: if head_piece == first_head:
if priority_increased < PRIORITY_INCREASE: if priority_increased < PRIORITY_INCREASE:
logger.debug('Everything we need has been scheduled, looking for pieces across file to unblacklist') logger.debug('Everything we need has been scheduled, looking for pieces across file to unblacklist')
for piece, status in enumerate(self.torrent.status.pieces[f.first_piece:f.last_piece+1], f.first_piece): for piece, status in enumerate(self.torrent.status.pieces[f.first_piece:f.last_piece+1], f.first_piece):
if status: if status:
continue continue
priority = self.torrent.handle.piece_priority(piece) priority = self.torrent.handle.piece_priority(piece)
if priority == 0: if priority == 0:
self.torrent.handle.piece_priority(piece, 1) self.torrent.handle.piece_priority(piece, 1)
@@ -493,11 +521,11 @@ class Torrent(object):
for piece, status in enumerate(self.torrent.status.pieces[f.first_piece:first_head], f.first_piece): for piece, status in enumerate(self.torrent.status.pieces[f.first_piece:first_head], f.first_piece):
if status or piece in currently_downloading: if status or piece in currently_downloading:
continue continue
if self.torrent.handle.piece_priority(piece) != 0: if self.torrent.handle.piece_priority(piece) != 0:
logger.debug('Blacklisting %i' % (piece, )) logger.debug('Blacklisting %i' % (piece, ))
self.torrent.handle.piece_priority(piece, 0) self.torrent.handle.piece_priority(piece, 0)
found_requested = False found_requested = False
for f in self.torrent_files: for f in self.torrent_files:
if f.file_requested_once: if f.file_requested_once:
@@ -508,40 +536,40 @@ class Torrent(object):
if found_requested: if found_requested:
logger.debug('Nobody is currently using %s, shutting down torrent-handler' % (self.infohash, )) logger.debug('Nobody is currently using %s, shutting down torrent-handler' % (self.infohash, ))
self.shutdown() self.shutdown()
reactor.callLater(1, self.update_piece_priority) reactor.callLater(1, self.update_piece_priority)
class TorrentHandler(object): class TorrentHandler(object):
def __init__(self, config): def __init__(self, config):
self.torrents = {} self.torrents = {}
self.config = config self.config = config
self.alerts = component.get("AlertManager") self.alerts = component.get("AlertManager")
self.alerts.register_handler("torrent_removed_alert", self.on_alert_torrent_removed) self.alerts.register_handler("torrent_removed_alert", self.on_alert_torrent_removed)
def get_stream(self, infohash, file_or_index=None, includes_name=False): def get_stream(self, infohash, file_or_index=None, includes_name=False):
logger.info('Trying to stream infohash %s and file %s include_name %s' % (infohash, file_or_index, includes_name)) logger.info('Trying to stream infohash %s and file %s include_name %s' % (infohash, file_or_index, includes_name))
if infohash not in self.torrents: if infohash not in self.torrents:
self.torrents[infohash] = Torrent(self, infohash) self.torrents[infohash] = Torrent(self, infohash)
return self.torrents[infohash].get_torrent_file(file_or_index, includes_name) return self.torrents[infohash].get_torrent_file(file_or_index, includes_name)
def on_alert_torrent_removed(self, alert): def on_alert_torrent_removed(self, alert):
try: try:
torrent_id = str(alert.handle.info_hash()) torrent_id = str(alert.handle.info_hash())
except (RuntimeError, KeyError): except (RuntimeError, KeyError):
logger.warning('Failed to handle on torrent remove alert') logger.warning('Failed to handle on torrent remove alert')
return return
if torrent_id not in self.torrents: if torrent_id not in self.torrents:
return return
self.torrents[torrent_id].shutdown() self.torrents[torrent_id].shutdown()
self.remove_torrent(torrent_id) self.remove_torrent(torrent_id)
def remove_torrent(self, torrent_id): def remove_torrent(self, torrent_id):
del self.torrents[torrent_id] del self.torrents[torrent_id]
def shutdown(self): def shutdown(self):
logger.debug('Shutting down TorrentHandler') logger.debug('Shutting down TorrentHandler')
self.alerts.deregister_handler(self.on_alert_torrent_removed) self.alerts.deregister_handler(self.on_alert_torrent_removed)
@@ -551,10 +579,10 @@ class TorrentHandler(object):
class Core(CorePluginBase): class Core(CorePluginBase):
listening = None listening = None
base_url = None base_url = None
def enable(self): def enable(self):
self.config = deluge.configmanager.ConfigManager("streaming.conf", DEFAULT_PREFS) self.config = deluge.configmanager.ConfigManager("streaming.conf", DEFAULT_PREFS)
try: try:
session = component.get("Core").session session = component.get("Core").session
settings = session.get_settings() settings = session.get_settings()
@@ -562,7 +590,7 @@ class Core(CorePluginBase):
session.set_settings(settings) session.set_settings(settings)
except AttributeError: except AttributeError:
logger.warning('Unable to exclude partial pieces') logger.warning('Unable to exclude partial pieces')
self.fsr = FileServeResource() self.fsr = FileServeResource()
resource = Resource() resource = Resource()
resource.putChild('file', self.fsr) resource.putChild('file', self.fsr)
@@ -570,29 +598,29 @@ class Core(CorePluginBase):
resource.putChild('stream', StreamResource(username=self.config['remote_username'], resource.putChild('stream', StreamResource(username=self.config['remote_username'],
password=self.config['remote_password'], password=self.config['remote_password'],
client=self)) client=self))
base_resource = Resource() base_resource = Resource()
base_resource.putChild('streaming', resource) base_resource.putChild('streaming', resource)
self.site = server.Site(base_resource) self.site = server.Site(base_resource)
self.torrent_handler = TorrentHandler(self.config) self.torrent_handler = TorrentHandler(self.config)
plugin_manager = component.get("CorePluginManager") plugin_manager = component.get("CorePluginManager")
logger.warning('plugins %s' % (plugin_manager.get_enabled_plugins(), )) logger.warning('plugins %s' % (plugin_manager.get_enabled_plugins(), ))
self.base_url = 'http' self.base_url = 'http'
if self.config['serve_method'] == 'standalone': if self.config['serve_method'] == 'standalone':
if self.config['use_ssl'] and self.check_ssl(): # use default deluge (or webui), input custom if self.config['use_ssl'] and self.check_ssl(): # use default deluge (or webui), input custom
if self.config['ssl_source'] == 'daemon': if self.config['ssl_source'] == 'daemon':
web_config = configmanager.ConfigManager("web.conf", {"pkey": "ssl/daemon.pkey", web_config = configmanager.ConfigManager("web.conf", {"pkey": "ssl/daemon.pkey",
"cert": "ssl/daemon.cert"}) "cert": "ssl/daemon.cert"})
context = ServerContextFactory(configmanager.get_config_dir(web_config['cert']), context = ServerContextFactory(configmanager.get_config_dir(web_config['cert']),
configmanager.get_config_dir(web_config['pkey'])) configmanager.get_config_dir(web_config['pkey']))
elif self.config['ssl_source'] == 'custom': elif self.config['ssl_source'] == 'custom':
context = ServerContextFactory(self.config['ssl_cert_path'], context = ServerContextFactory(self.config['ssl_cert_path'],
self.config['ssl_priv_key_path']) self.config['ssl_priv_key_path'])
try: try:
self.listening = reactor.listenSSL(self.config['port'], self.site, context, interface=self.config['ip']) self.listening = reactor.listenSSL(self.config['port'], self.site, context, interface=self.config['ip'])
except: except:
@@ -603,22 +631,22 @@ class Core(CorePluginBase):
self.listening = reactor.listenTCP(self.config['port'], self.site, interface=self.config['ip']) self.listening = reactor.listenTCP(self.config['port'], self.site, interface=self.config['ip'])
except: except:
self.listening = reactor.listenTCP(self.config['port'], self.site, interface='0.0.0.0') self.listening = reactor.listenTCP(self.config['port'], self.site, interface='0.0.0.0')
port = self.config['port'] port = self.config['port']
ip = self.config['ip'] ip = self.config['ip']
elif self.config['serve_method'] == 'webui': # this webserver is fubar elif self.config['serve_method'] == 'webui': # this webserver is fubar
plugin_manager = component.get("CorePluginManager") plugin_manager = component.get("CorePluginManager")
webui_plugin = plugin_manager['WebUi'].plugin webui_plugin = plugin_manager['WebUi'].plugin
webui_plugin.server.top_level.putChild('streaming', resource) webui_plugin.server.top_level.putChild('streaming', resource)
port = webui_plugin.server.port port = webui_plugin.server.port
ip = getattr(webui_plugin.server, 'interface', None) or self.config['ip'] ip = getattr(webui_plugin.server, 'interface', None) or self.config['ip']
if webui_plugin.server.https: if webui_plugin.server.https:
self.base_url += 's' self.base_url += 's'
else: else:
raise NotImplementedError() raise NotImplementedError()
self.base_url += '://' self.base_url += '://'
if ':' in ip: if ':' in ip:
self.base_url += ip self.base_url += ip
@@ -629,15 +657,15 @@ class Core(CorePluginBase):
def disable(self): def disable(self):
self.site.stopFactory() self.site.stopFactory()
self.torrent_handler.shutdown() self.torrent_handler.shutdown()
plugin_manager = component.get("CorePluginManager") plugin_manager = component.get("CorePluginManager")
webui_plugin = plugin_manager['WebUi'].plugin webui_plugin = plugin_manager['WebUi'].plugin
try: try:
webui_plugin.server.top_level.delEntity('streaming') webui_plugin.server.top_level.delEntity('streaming')
except KeyError: except KeyError:
pass pass
if self.listening: if self.listening:
yield self.listening.stopListening() yield self.listening.stopListening()
self.listening = None self.listening = None
@@ -648,34 +676,34 @@ class Core(CorePluginBase):
def check_ssl(self): def check_ssl(self):
if self.config['ssl_source'] == 'daemon': if self.config['ssl_source'] == 'daemon':
return True return True
if not os.path.isfile(self.config['ssl_priv_key_path']) or not os.access(self.config['ssl_priv_key_path'], os.R_OK): if not os.path.isfile(self.config['ssl_priv_key_path']) or not os.access(self.config['ssl_priv_key_path'], os.R_OK):
return False return False
if not os.path.isfile(self.config['ssl_cert_path']) or not os.access(self.config['ssl_cert_path'], os.R_OK): if not os.path.isfile(self.config['ssl_cert_path']) or not os.access(self.config['ssl_cert_path'], os.R_OK):
return False return False
return True return True
def check_webui(self): def check_webui(self):
plugin_manager = component.get("CorePluginManager") plugin_manager = component.get("CorePluginManager")
return 'WebUi' in plugin_manager.get_enabled_plugins() return 'WebUi' in plugin_manager.get_enabled_plugins()
def check_config(self): def check_config(self):
pass pass
@export @export
@defer.inlineCallbacks @defer.inlineCallbacks
def set_config(self, config): def set_config(self, config):
self.previous_config = copy(self.config) self.previous_config = copy(self.config)
for key in config.keys(): for key in config.keys():
self.config[key] = config[key] self.config[key] = config[key]
self.config.save() self.config.save()
yield self.disable() yield self.disable()
self.enable() self.enable()
if self.config['serve_method'] == 'standalone' and self.config['ssl_source'] == 'custom' and self.config['use_ssl']: if self.config['serve_method'] == 'standalone' and self.config['ssl_source'] == 'custom' and self.config['use_ssl']:
if not self.check_ssl(): if not self.check_ssl():
defer.returnValue(('error', 'ssl', 'SSL not enabled, make sure the private key and certificate exist and are accessible')) defer.returnValue(('error', 'ssl', 'SSL not enabled, make sure the private key and certificate exist and are accessible'))
@@ -684,43 +712,45 @@ class Core(CorePluginBase):
def get_config(self): def get_config(self):
"""Returns the config dictionary""" """Returns the config dictionary"""
return self.config.config return self.config.config
@export @export
@defer.inlineCallbacks @defer.inlineCallbacks
def stream_torrent(self, infohash=None, url=None, filedump=None, filepath_or_index=None, includes_name=False, wait_for_end_pieces=False): def stream_torrent(self, infohash=None, url=None, filedump=None, filepath_or_index=None, includes_name=False, wait_for_end_pieces=False):
tor = component.get("TorrentManager").torrents.get(infohash, None) tor = component.get("TorrentManager").torrents.get(infohash, None)
if tor is None: if tor is None:
logger.info('Did not find torrent, must add it') logger.info('Did not find torrent, must add it')
if not filedump and url: if not filedump and url:
filedump = yield client.getPage(url) filedump = yield client.getPage(url)
if not filedump: if not filedump:
defer.returnValue({'status': 'error', 'message': 'unable to find torrent, provide infohash, url or filedump'}) defer.returnValue({'status': 'error', 'message': 'unable to find torrent, provide infohash, url or filedump'})
torrent_info = lt.torrent_info(lt.bdecode(filedump)) torrent_info = lt.torrent_info(lt.bdecode(filedump))
infohash = str(torrent_info.info_hash()) infohash = str(torrent_info.info_hash())
core = component.get("Core") core = component.get("Core")
try: try:
yield core.add_torrent_file('file.torrent', filedump.encode('base64'), {'add_paused': True}) yield core.add_torrent_file('file.torrent', filedump.encode('base64'), {'add_paused': True})
except: except:
defer.returnValue({'status': 'error', 'message': 'failed to add torrent'}) defer.returnValue({'status': 'error', 'message': 'failed to add torrent'})
try: try:
tf = self.torrent_handler.get_stream(infohash, filepath_or_index, includes_name) tf = self.torrent_handler.get_stream(infohash, filepath_or_index, includes_name)
except UnknownTorrentException: except UnknownTorrentException:
defer.returnValue({'status': 'error', 'message': 'unable to find torrent, probably failed to add it'}) defer.returnValue({'status': 'error', 'message': 'unable to find torrent, probably failed to add it'})
if wait_for_end_pieces: if wait_for_end_pieces:
logger.debug('Waiting for end pieces') logger.debug('Waiting for end pieces')
yield tf.wait_for_end_pieces() yield tf.wait_for_end_pieces()
filename = os.path.basename(tf.path).encode('utf-8')
defer.returnValue({ defer.returnValue({
'status': 'success', 'status': 'success',
'filename': filename,
'use_stream_urls': self.config['use_stream_urls'], 'use_stream_urls': self.config['use_stream_urls'],
'auto_open_stream_urls': self.config['auto_open_stream_urls'], 'auto_open_stream_urls': self.config['auto_open_stream_urls'],
'url': '%s/streaming/file/%s/%s' % (self.base_url, self.fsr.add_file(tf), 'url': '%s/streaming/file/%s/%s' % (self.base_url, self.fsr.add_file(tf),
urllib.quote_plus(os.path.basename(tf.path).encode('utf-8'))) urllib.quote_plus(filename))
}) })

View File

@@ -519,6 +519,48 @@
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<widget class="GtkHBox" id="remote_url_hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<widget class="GtkLabel" id="remote_url_label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remote control url:</property>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="output_remote_url">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">•</property>
<property name="invisible_char_set">True</property>
<property name="primary_icon_activatable">False</property>
<property name="secondary_icon_activatable">False</property>
<property name="primary_icon_sensitive">True</property>
<property name="secondary_icon_sensitive">True</property>
<property name="editable">False</property>
</widget>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</widget>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</widget> </widget>
</child> </child>
</widget> </widget>

View File

@@ -37,13 +37,13 @@ PreferencePage = Ext.extend(Ext.Panel, {
layout: 'form', layout: 'form',
autoScroll: true, autoScroll: true,
_fields: {}, _fields: {},
initComponent: function() { initComponent: function() {
PreferencePage.superclass.initComponent.call(this); PreferencePage.superclass.initComponent.call(this);
var om = this.optionsManager = new Deluge.OptionsManager(); var om = this.optionsManager = new Deluge.OptionsManager();
this.on('show', this.onPageShow, this); this.on('show', this.onPageShow, this);
var fieldset = this.add({ var fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -56,13 +56,13 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 180, width: 180,
} }
}); });
om.bind('download_only_streamed', fieldset.add({ om.bind('download_only_streamed', fieldset.add({
xtype: 'checkbox', xtype: 'checkbox',
name: 'download_only_streamed', name: 'download_only_streamed',
boxLabel: 'Download only streamed files, skip the other files', boxLabel: 'Download only streamed files, skip the other files',
})); }));
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -75,12 +75,12 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 180, width: 180,
} }
}); });
om.bind('ip', fieldset.add({ om.bind('ip', fieldset.add({
name: 'ip', name: 'ip',
fieldLabel: 'Hostname', fieldLabel: 'Hostname',
})); }));
om.bind('port', fieldset.add({ om.bind('port', fieldset.add({
name: 'port', name: 'port',
fieldLabel: _('Port'), fieldLabel: _('Port'),
@@ -88,7 +88,7 @@ PreferencePage = Ext.extend(Ext.Panel, {
minValue: -1, minValue: -1,
maxValue: 99999, maxValue: 99999,
})); }));
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -98,16 +98,16 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 240, width: 240,
labelWidth: 1 labelWidth: 1
}); });
this._fields['serve_method_webui'] = fieldset.add({ this._fields['serve_method_webui'] = fieldset.add({
name: 'serve_method', name: 'serve_method',
boxLabel: 'Serve files via WebUI', boxLabel: 'Serve files via WebUI',
inputValue: 'webui', inputValue: 'webui',
disabled: true disabled: true
}); });
om.bind('serve_method', this._fields['serve_method_webui']); om.bind('serve_method', this._fields['serve_method_webui']);
this._fields['serve_method_standalone'] = fieldset.add({ this._fields['serve_method_standalone'] = fieldset.add({
name: 'serve_method', name: 'serve_method',
boxLabel: 'Serve files via standalone', boxLabel: 'Serve files via standalone',
@@ -115,15 +115,15 @@ PreferencePage = Ext.extend(Ext.Panel, {
disabled: true disabled: true
}); });
om.bind('serve_method', this._fields['serve_method_standalone']); om.bind('serve_method', this._fields['serve_method_standalone']);
om.bind('use_ssl', fieldset.add({ om.bind('use_ssl', fieldset.add({
xtype: 'checkbox', xtype: 'checkbox',
name: 'use_ssl', name: 'use_ssl',
boxLabel: 'Use SSL', boxLabel: 'Use SSL',
style: 'margin-left: 12px;' style: 'margin-left: 12px;'
})); }));
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -133,7 +133,7 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 240, width: 240,
labelWidth: 1 labelWidth: 1
}); });
this._fields['ssl_source_daemon'] = fieldset.add({ this._fields['ssl_source_daemon'] = fieldset.add({
name: 'ssl_source', name: 'ssl_source',
boxLabel: 'Use Daemon/WebUI Certificate', boxLabel: 'Use Daemon/WebUI Certificate',
@@ -141,7 +141,7 @@ PreferencePage = Ext.extend(Ext.Panel, {
value: 'daemon' value: 'daemon'
}) })
om.bind('ssl_source', this._fields['ssl_source_daemon']); om.bind('ssl_source', this._fields['ssl_source_daemon']);
this._fields['ssl_source_custom'] = fieldset.add({ this._fields['ssl_source_custom'] = fieldset.add({
name: 'ssl_source', name: 'ssl_source',
boxLabel: 'Custom Certificate', boxLabel: 'Custom Certificate',
@@ -149,7 +149,7 @@ PreferencePage = Ext.extend(Ext.Panel, {
value: 'custom' value: 'custom'
}); });
om.bind('ssl_source', this._fields['ssl_source_custom']); om.bind('ssl_source', this._fields['ssl_source_custom']);
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -161,17 +161,17 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 130, width: 130,
} }
}); });
om.bind('ssl_priv_key_path', fieldset.add({ om.bind('ssl_priv_key_path', fieldset.add({
name: 'ssl_priv_key_path', name: 'ssl_priv_key_path',
fieldLabel: 'Private key file path' fieldLabel: 'Private key file path'
})); }));
om.bind('ssl_cert_path', fieldset.add({ om.bind('ssl_cert_path', fieldset.add({
name: 'ssl_cert_path', name: 'ssl_cert_path',
fieldLabel: 'Certificate and chains file path' fieldLabel: 'Certificate and chains file path'
})); }));
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -184,14 +184,14 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 180, width: 180,
} }
}); });
om.bind('allow_remote', fieldset.add({ om.bind('allow_remote', fieldset.add({
xtype: 'checkbox', xtype: 'checkbox',
name: 'allow_remote', name: 'allow_remote',
boxLabel: 'Allow remote control', boxLabel: 'Allow remote control',
style: 'margin-left: 12px;' style: 'margin-left: 12px;'
})); }));
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -203,19 +203,28 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 180, width: 180,
} }
}); });
om.bind('remote_username', fieldset.add({ om.bind('remote_username', fieldset.add({
xtype: 'textfield', xtype: 'textfield',
name: 'remote_username', name: 'remote_username',
fieldLabel: 'Remote control username' fieldLabel: 'Remote control username'
})); }));
om.bind('remote_password', fieldset.add({ om.bind('remote_password', fieldset.add({
xtype: 'textfield', xtype: 'textfield',
inputType: 'password',
name: 'remote_password', name: 'remote_password',
fieldLabel: 'Remote control password' fieldLabel: 'Remote control password'
})); }));
fieldset.add({
xtype: 'textfield',
id: 'remote_url',
name: 'remote_url',
readOnly: true,
fieldLabel: 'Remote control url'
});
fieldset = this.add({ fieldset = this.add({
xtype: 'fieldset', xtype: 'fieldset',
border: false, border: false,
@@ -227,14 +236,14 @@ PreferencePage = Ext.extend(Ext.Panel, {
width: 180, width: 180,
} }
}); });
om.bind('use_stream_urls', fieldset.add({ om.bind('use_stream_urls', fieldset.add({
xtype: 'checkbox', xtype: 'checkbox',
name: 'use_stream_urls', name: 'use_stream_urls',
boxLabel: 'Use stream urls', boxLabel: 'Use stream urls',
style: 'margin-left: 12px;' style: 'margin-left: 12px;'
})); }));
om.bind('auto_open_stream_urls', fieldset.add({ om.bind('auto_open_stream_urls', fieldset.add({
xtype: 'checkbox', xtype: 'checkbox',
name: 'auto_open_stream_urls', name: 'auto_open_stream_urls',
@@ -244,7 +253,7 @@ PreferencePage = Ext.extend(Ext.Panel, {
}, },
onApply: function() { onApply: function() {
var changed = this.optionsManager.getDirty(); var changed = this.optionsManager.getDirty();
for (var key in this._fields) { for (var key in this._fields) {
if (this._fields.hasOwnProperty(key)) { if (this._fields.hasOwnProperty(key)) {
@@ -259,13 +268,13 @@ PreferencePage = Ext.extend(Ext.Panel, {
success: this.onSetConfig, success: this.onSetConfig,
scope: this scope: this
}); });
for (var key in deluge.config) { for (var key in deluge.config) {
deluge.config[key] = this.optionsManager.get(key); deluge.config[key] = this.optionsManager.get(key);
} }
} }
}, },
onSetConfig: function(result) { onSetConfig: function(result) {
this.optionsManager.commit(); this.optionsManager.commit();
if (result) { if (result) {
@@ -280,17 +289,27 @@ PreferencePage = Ext.extend(Ext.Panel, {
Ext.Msg.alert(topic, message); Ext.Msg.alert(topic, message);
} }
} }
this.updateRemoteUrl(this.optionsManager);
}, },
onGotConfig: function(config) { onGotConfig: function(config) {
this.optionsManager.set(config); this.optionsManager.set(config);
this.updateRemoteUrl(this.optionsManager);
}, },
onPageShow: function() { onPageShow: function() {
deluge.client.streaming.get_config({ deluge.client.streaming.get_config({
success: this.onGotConfig, success: this.onGotConfig,
scope: this scope: this
}) })
},
updateRemoteUrl: function(optionsManager) {
var apiUrl = 'http';
if (optionsManager.get('use_ssl'))
apiUrl += 's';
apiUrl += '://' + optionsManager.get('ip') + ':' + optionsManager.get('port') + '/streaming/stream';
Ext.getCmp('remote_url').setValue(apiUrl);
} }
}); });
@@ -299,7 +318,7 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
onDisable: function() { onDisable: function() {
deluge.menus.filePriorities.remove('streamthis'); deluge.menus.filePriorities.remove('streamthis');
deluge.preferences.selectPage(_('Plugins')); deluge.preferences.selectPage(_('Plugins'));
deluge.preferences.removePage(this.prefsPage); deluge.preferences.removePage(this.prefsPage);
this.prefsPage.destroy(); this.prefsPage.destroy();
@@ -308,7 +327,7 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
onEnable: function() { onEnable: function() {
this.prefsPage = new PreferencePage(); this.prefsPage = new PreferencePage();
deluge.preferences.addPage(this.prefsPage); deluge.preferences.addPage(this.prefsPage);
console.log('Streaming plugin loaded'); console.log('Streaming plugin loaded');
var doStream = function (tid, fileIndex) { var doStream = function (tid, fileIndex) {
deluge.client.streaming.stream_torrent(tid, null, null, fileIndex, true, { deluge.client.streaming.stream_torrent(tid, null, null, fileIndex, true, {
@@ -330,8 +349,8 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
} }
}) })
} }
deluge.menus.filePriorities.addMenuItem({ deluge.menus.filePriorities.addMenuItem({
id: 'streamthis', id: 'streamthis',
text: 'Stream this file', text: 'Stream this file',
@@ -350,7 +369,7 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
return false; return false;
} }
}); });
deluge.menus.torrent.addMenuItem({ deluge.menus.torrent.addMenuItem({
id: 'streamthistorrent', id: 'streamthistorrent',
text: 'Stream this torrent', text: 'Stream this torrent',

View File

@@ -56,31 +56,31 @@ from common import get_resource
class LocalAddResource(resource.Resource): class LocalAddResource(resource.Resource):
gtkui = None gtkui = None
isLeaf = True isLeaf = True
def __init__(self, gtkui): def __init__(self, gtkui):
self.gtkui = gtkui self.gtkui = gtkui
resource.Resource.__init__(self) resource.Resource.__init__(self)
def render_GET(self, request): def render_GET(self, request):
useragent = request.getHeader('User-Agent') useragent = request.getHeader('User-Agent')
if 'Deluge-Streamer' not in useragent: if 'Deluge-Streamer' not in useragent:
request.setResponseCode(401) request.setResponseCode(401)
return 'Unauthorized' return 'Unauthorized'
torrent_url = request.args.get('url', None) torrent_url = request.args.get('url', None)
if not torrent_url: if not torrent_url:
return json.dumps({'status': 'error', 'message': 'missing url in request'}) return json.dumps({'status': 'error', 'message': 'missing url in request'})
torrent_file = request.args.get('file', None) torrent_file = request.args.get('file', None)
if torrent_file: if torrent_file:
torrent_file = torrent_file[0] torrent_file = torrent_file[0]
infohash = request.args.get('infohash', None) infohash = request.args.get('infohash', None)
if infohash: if infohash:
infohash = infohash[0] infohash = infohash[0]
client.streaming.stream_torrent(url=torrent_url[0], infohash=infohash, filepath_or_index=torrent_file).addCallback(self.gtkui.stream_ready) client.streaming.stream_torrent(url=torrent_url[0], infohash=infohash, filepath_or_index=torrent_file).addCallback(self.gtkui.stream_ready)
return json.dumps({'status': 'ok', 'message': 'queued'}) return json.dumps({'status': 'ok', 'message': 'queued'})
class GtkUI(GtkPluginBase): class GtkUI(GtkPluginBase):
@@ -90,31 +90,31 @@ class GtkUI(GtkPluginBase):
component.get("Preferences").add_page("Streaming", self.glade.get_widget("prefs_box")) component.get("Preferences").add_page("Streaming", self.glade.get_widget("prefs_box"))
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs) component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_prefs)
component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs) component.get("PluginManager").register_hook("on_show_prefs", self.on_show_prefs)
file_menu = component.get("MainWindow").main_glade.get_widget('menu_file_tab') file_menu = component.get("MainWindow").main_glade.get_widget('menu_file_tab')
self.sep = gtk.SeparatorMenuItem() self.sep = gtk.SeparatorMenuItem()
self.item = gtk.MenuItem(_("_Stream this file")) self.item = gtk.MenuItem(_("_Stream this file"))
self.item.connect("activate", self.on_menuitem_stream) self.item.connect("activate", self.on_menuitem_stream)
file_menu.append(self.sep) file_menu.append(self.sep)
file_menu.append(self.item) file_menu.append(self.item)
self.sep.show() self.sep.show()
self.item.show() self.item.show()
torrentmenu = component.get("MenuBar").torrentmenu torrentmenu = component.get("MenuBar").torrentmenu
self.sep_torrentmenu = gtk.SeparatorMenuItem() self.sep_torrentmenu = gtk.SeparatorMenuItem()
self.item_torrentmenu = gtk.MenuItem(_("_Stream this torrent")) self.item_torrentmenu = gtk.MenuItem(_("_Stream this torrent"))
self.item_torrentmenu.connect("activate", self.on_torrentmenu_menuitem_stream) self.item_torrentmenu.connect("activate", self.on_torrentmenu_menuitem_stream)
torrentmenu.append(self.sep_torrentmenu) torrentmenu.append(self.sep_torrentmenu)
torrentmenu.append(self.item_torrentmenu) torrentmenu.append(self.item_torrentmenu)
self.sep_torrentmenu.show() self.sep_torrentmenu.show()
self.item_torrentmenu.show() self.item_torrentmenu.show()
self.resource = LocalAddResource(self) self.resource = LocalAddResource(self)
self.site = server.Site(self.resource) self.site = server.Site(self.resource)
self.listening = reactor.listenTCP(40747, self.site, interface='127.0.0.1') self.listening = reactor.listenTCP(40747, self.site, interface='127.0.0.1')
@@ -124,34 +124,34 @@ class GtkUI(GtkPluginBase):
component.get("Preferences").remove_page("Streaming") component.get("Preferences").remove_page("Streaming")
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs) component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_prefs)
component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs) component.get("PluginManager").deregister_hook("on_show_prefs", self.on_show_prefs)
file_menu = component.get("MainWindow").main_glade.get_widget('menu_file_tab') file_menu = component.get("MainWindow").main_glade.get_widget('menu_file_tab')
file_menu.remove(self.item) file_menu.remove(self.item)
file_menu.remove(self.sep) file_menu.remove(self.sep)
torrentmenu = component.get("MenuBar").torrentmenu torrentmenu = component.get("MenuBar").torrentmenu
torrentmenu.remove(self.item_torrentmenu) torrentmenu.remove(self.item_torrentmenu)
torrentmenu.remove(self.sep_torrentmenu) torrentmenu.remove(self.sep_torrentmenu)
self.site.stopFactory() self.site.stopFactory()
yield self.listening.stopListening() yield self.listening.stopListening()
@defer.inlineCallbacks @defer.inlineCallbacks
def on_apply_prefs(self): def on_apply_prefs(self):
log.debug("applying prefs for Streaming") log.debug("applying prefs for Streaming")
if self.glade.get_widget("input_serve_standalone").get_active(): if self.glade.get_widget("input_serve_standalone").get_active():
serve_method = 'standalone' serve_method = 'standalone'
elif self.glade.get_widget("input_serve_webui").get_active(): elif self.glade.get_widget("input_serve_webui").get_active():
serve_method = 'webui' serve_method = 'webui'
if self.glade.get_widget("input_ssl_cert_daemon").get_active(): if self.glade.get_widget("input_ssl_cert_daemon").get_active():
ssl_source = 'daemon' ssl_source = 'daemon'
elif self.glade.get_widget("input_ssl_cert_custom").get_active(): elif self.glade.get_widget("input_ssl_cert_custom").get_active():
ssl_source = 'custom' ssl_source = 'custom'
config = { config = {
"ip": self.glade.get_widget("input_ip").get_text(), "ip": self.glade.get_widget("input_ip").get_text(),
"port": int(self.glade.get_widget("input_port").get_text()), "port": int(self.glade.get_widget("input_port").get_text()),
@@ -167,16 +167,16 @@ class GtkUI(GtkPluginBase):
"serve_method": serve_method, "serve_method": serve_method,
"ssl_source": ssl_source, "ssl_source": ssl_source,
} }
result = yield client.streaming.set_config(config) result = yield client.streaming.set_config(config)
if result: if result:
message_type, message_class, message = result message_type, message_class, message = result
if message_type == 'error': if message_type == 'error':
topic = 'Unknown error type' topic = 'Unknown error type'
if message_class == 'ssl': if message_class == 'ssl':
topic = 'SSL Failed' topic = 'SSL Failed'
dialogs.ErrorDialog(topic, message).run() dialogs.ErrorDialog(topic, message).run()
def on_show_prefs(self): def on_show_prefs(self):
@@ -195,13 +195,16 @@ class GtkUI(GtkPluginBase):
self.glade.get_widget("input_remote_password").set_text(config["remote_password"]) self.glade.get_widget("input_remote_password").set_text(config["remote_password"])
self.glade.get_widget("input_ssl_priv_key_path").set_text(config["ssl_priv_key_path"]) self.glade.get_widget("input_ssl_priv_key_path").set_text(config["ssl_priv_key_path"])
self.glade.get_widget("input_ssl_cert_path").set_text(config["ssl_cert_path"]) self.glade.get_widget("input_ssl_cert_path").set_text(config["ssl_cert_path"])
self.glade.get_widget("input_serve_standalone").set_active(config["serve_method"] == "standalone") self.glade.get_widget("input_serve_standalone").set_active(config["serve_method"] == "standalone")
self.glade.get_widget("input_serve_webui").set_active(config["serve_method"] == "webui") self.glade.get_widget("input_serve_webui").set_active(config["serve_method"] == "webui")
self.glade.get_widget("input_ssl_cert_daemon").set_active(config["ssl_source"] == "daemon") self.glade.get_widget("input_ssl_cert_daemon").set_active(config["ssl_source"] == "daemon")
self.glade.get_widget("input_ssl_cert_custom").set_active(config["ssl_source"] == "custom") self.glade.get_widget("input_ssl_cert_custom").set_active(config["ssl_source"] == "custom")
api_url = 'http%s://%s:%s/streaming/stream' % (('s' if config["use_ssl"] else ''), config["ip"], config["port"])
self.glade.get_widget("output_remote_url").set_text(api_url)
def stream_ready(self, result): def stream_ready(self, result):
if result['status'] == 'success': if result['status'] == 'success':
if result.get('use_stream_urls', False): if result.get('use_stream_urls', False):
@@ -217,19 +220,19 @@ class GtkUI(GtkPluginBase):
def on_menuitem_stream(self, data=None): def on_menuitem_stream(self, data=None):
torrent_id = component.get("TorrentView").get_selected_torrents()[0] torrent_id = component.get("TorrentView").get_selected_torrents()[0]
ft = component.get("TorrentDetails").tabs['Files'] ft = component.get("TorrentDetails").tabs['Files']
paths = ft.listview.get_selection().get_selected_rows()[1] paths = ft.listview.get_selection().get_selected_rows()[1]
selected = [] selected = []
for path in paths: for path in paths:
selected.append(ft.treestore.get_iter(path)) selected.append(ft.treestore.get_iter(path))
for select in selected: for select in selected:
path = ft.get_file_path(select) path = ft.get_file_path(select)
client.streaming.stream_torrent(infohash=torrent_id, filepath_or_index=path, includes_name=True).addCallback(self.stream_ready) client.streaming.stream_torrent(infohash=torrent_id, filepath_or_index=path, includes_name=True).addCallback(self.stream_ready)
break break
def on_torrentmenu_menuitem_stream(self, data=None): def on_torrentmenu_menuitem_stream(self, data=None):
torrent_id = component.get("TorrentView").get_selected_torrents()[0] torrent_id = component.get("TorrentView").get_selected_torrents()[0]
client.streaming.stream_torrent(infohash=torrent_id).addCallback(self.stream_ready) client.streaming.stream_torrent(infohash=torrent_id).addCallback(self.stream_ready)