6 Commits
0.8.1 ... 0.9.0

Author SHA1 Message Date
Anders Jensen
1aed78f389 Merge branch 'release/0.9.0' 2018-02-20 17:57:18 +01:00
Anders Jensen
e1db68012b bumped version, fixed changelog 2018-02-20 17:57:12 +01:00
Anders Jensen
3c30163582 fixed small problem with Deluge 2 2018-02-20 17:43:23 +01:00
Anders Jensen
e6948fa90f a bit of cleanup 2017-12-17 19:38:53 +01:00
Anders Jensen
84dd3c4fe7 fixed bug where multiple readers of same piece caused errors 2017-12-17 19:30:39 +01:00
JohnDoee
ea96d0f739 Merge tag '0.8.1' into develop
small fixes
2017-08-19 12:13:36 +02:00
8 changed files with 90 additions and 67 deletions

View File

@@ -44,6 +44,10 @@ The _allow remote_ option is to allow remote add and stream of torrents.
# Version Info
## Version 0.9.0
* Few bugfixes
* Added support for Deluge 2
## Version 0.8.1
* Fixed some small problems and bugs
* better URL execution with GTKUI

View File

@@ -40,9 +40,9 @@
from setuptools import setup
__plugin_name__ = "Streaming"
__author__ = "John Doee"
__author__ = "Anders Jensen"
__author_email__ = "johndoee@tidalstream.org"
__version__ = "0.8.1"
__version__ = "0.9.0"
__url__ = "https://github.com/JohnDoee/deluge-streaming"
__license__ = "GPLv3"
__description__ = "Enables streaming of files while downloading them."

View File

@@ -39,18 +39,21 @@
from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase):
def __init__(self, plugin_name):
from core import Core as _plugin_cls
self._plugin_cls = _plugin_cls
super(CorePlugin, self).__init__(plugin_name)
class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from gtkui import GtkUI as _plugin_cls
self._plugin_cls = _plugin_cls
super(GtkUIPlugin, self).__init__(plugin_name)
class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from webui import WebUI as _plugin_cls

View File

@@ -50,12 +50,12 @@ from copy import copy
from deluge import component, configmanager
from deluge._libtorrent import lt
from deluge.core.rpcserver import export, check_ssl_keys
from deluge.core.rpcserver import export
from deluge.plugins.pluginbase import CorePluginBase
from twisted.internet import reactor, defer, task
from twisted.internet import reactor, defer
from twisted.python import randbytes
from twisted.web import server, resource, static, http, client
from twisted.web import server, resource, static, client
from .filelike import FilelikeObjectResource
from .resource import Resource
@@ -80,11 +80,13 @@ DEFAULT_PREFS = {
PRIORITY_INCREASE = 5
def sleep(seconds):
d = defer.Deferred()
reactor.callLater(seconds, d.callback, seconds)
return d
class ServerContextFactory(object):
def __init__(self, cert_file, key_file):
self._cert_file = cert_file
@@ -103,6 +105,7 @@ class ServerContextFactory(object):
ctx.use_privatekey_file(self._key_file)
return ctx
class FileServeResource(resource.Resource):
isLeaf = True
@@ -131,6 +134,7 @@ class FileServeResource(resource.Resource):
tfr = f.open()
return FilelikeObjectResource(tfr, f.size).render_GET(request)
class StreamResource(Resource):
isLeaf = True
@@ -180,12 +184,15 @@ class StreamResource(Resource):
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))
class UnknownTorrentException(Exception):
pass
class UnknownFileException(Exception):
pass
class TorrentFileReader(object):
def __init__(self, torrent_file):
self.torrent_file = torrent_file
@@ -207,7 +214,7 @@ class TorrentFileReader(object):
self.current_piece = required_piece
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 - size of current payload %s' % (read_position, size, self.position, len(self.current_piece_data)))
data = self.current_piece_data[read_position:read_position+size]
self.position += len(data)
@@ -222,7 +229,8 @@ class TorrentFileReader(object):
def seek(self, offset, whence=os.SEEK_SET):
self.position = offset
class TorrentFile(object): # can be read from, knows about itself
class TorrentFile(object): # can be read from, knows about itself
def __init__(self, torrent, first_piece, last_piece, piece_size, offset, path, full_path, size, index):
self.torrent = torrent
self.first_piece = first_piece
@@ -244,7 +252,6 @@ class TorrentFile(object): # can be read from, knows about itself
self.alerts = component.get("AlertManager")
def open(self):
"""
Returns a filelike object
@@ -265,7 +272,7 @@ class TorrentFile(object): # can be read from, knows about itself
def is_complete(self):
torrent_status = self.torrent.torrent.get_status(['file_progress', 'state'])
file_progress = torrent_status['file_progress']
return file_progress[self.index] == 1.0
return file_progress and file_progress[self.index] == 1.0
def get_piece_info(self, tell):
return divmod((self.offset + tell), self.piece_size)
@@ -284,7 +291,10 @@ class TorrentFile(object): # can be read from, knows about itself
return
piece_data = copy(alert.buffer)
self.waiting_pieces[alert.piece].callback(piece_data)
cbs = self.waiting_pieces.pop(alert.piece, [])
for cb in cbs:
cb.callback(piece_data)
@defer.inlineCallbacks
def wait_for_end_pieces(self):
@@ -307,7 +317,13 @@ class TorrentFile(object): # can be read from, knows about itself
defer.returnValue(reader.current_piece_data)
if piece not in self.waiting_pieces:
self.waiting_pieces[piece] = defer.Deferred()
created_waiting_defer = True
self.waiting_pieces[piece] = []
else:
created_waiting_defer = False
d = defer.Deferred()
self.waiting_pieces[piece].append(d)
logger.debug('Waiting for %s' % piece)
while not self.torrent.torrent.handle.have_piece(piece):
@@ -316,17 +332,17 @@ class TorrentFile(object): # can be read from, knows about itself
logger.debug('Did not have piece %i, waiting' % piece)
yield sleep(1)
self.torrent.torrent.handle.read_piece(piece)
if created_waiting_defer:
self.torrent.torrent.handle.read_piece(piece)
data = yield self.waiting_pieces[piece]
if piece in self.waiting_pieces:
del self.waiting_pieces[piece]
data = yield d
logger.debug('Done waiting for piece %i, returning data' % piece)
defer.returnValue(data)
def shutdown(self):
self.do_shutdown = True
class Torrent(object):
def __init__(self, torrent_handler, infohash):
self.infohash = infohash
@@ -339,8 +355,7 @@ class Torrent(object):
self.torrent_files = None
self.priority_increased = defaultdict(set)
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.file_priorities = [0] * len(self.torrent_files)
@@ -419,7 +434,7 @@ class Torrent(object):
if self.torrent_released:
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)
return f
@@ -448,7 +463,7 @@ class Torrent(object):
for tf in self.torrent_files:
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:
return
@@ -456,13 +471,13 @@ class Torrent(object):
currently_downloading = self.get_currently_downloading()
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
logger.debug('Rescheduling file %s' % (f.path, ))
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)
waiting_for_pieces = set()
@@ -472,7 +487,7 @@ class Torrent(object):
waiting_for_pieces.add(tfr.waiting_for_piece)
piece = max(tfr.waiting_for_piece, tfr.current_piece)
if piece is not None:
if piece is not None:
heads.add(piece)
if not heads:
@@ -483,7 +498,6 @@ class Torrent(object):
for head_piece in heads:
priority_increased = 0
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)))
if status or piece in currently_downloading:
continue
@@ -539,6 +553,7 @@ class Torrent(object):
reactor.callLater(1, self.update_piece_priority)
class TorrentHandler(object):
def __init__(self, config):
self.torrents = {}
@@ -576,6 +591,7 @@ class TorrentHandler(object):
for torrent in self.torrents.values():
torrent.shutdown()
class Core(CorePluginBase):
listening = None
base_url = None
@@ -610,7 +626,7 @@ class Core(CorePluginBase):
self.base_url = 'http'
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':
web_config = configmanager.ConfigManager("web.conf", {"pkey": "ssl/daemon.pkey",
"cert": "ssl/daemon.cert"})
@@ -634,7 +650,7 @@ class Core(CorePluginBase):
port = self.config['port']
ip = self.config['ip']
elif self.config['serve_method'] == 'webui' and self.check_webui(): # this webserver is fubar
elif self.config['serve_method'] == 'webui' and self.check_webui(): # this webserver is fubar
plugin_manager = component.get("CorePluginManager")
webui_plugin = plugin_manager['WebUi'].plugin
@@ -754,4 +770,4 @@ class Core(CorePluginBase):
'auto_open_stream_urls': self.config['auto_open_stream_urls'],
'url': '%s/streaming/file/%s/%s' % (self.base_url, self.fsr.add_file(tf),
urllib.quote_plus(filename))
})
})

View File

@@ -2,22 +2,18 @@ from twisted.internet import defer
from twisted.python import log
from twisted.web import http, resource, server, static
# NOTICE!
# All these producers are taken directly from the Twisted Project.
# This is because i needed to make them accept defers.
# /NOTICE!
class NoRangeStaticProducer(static.NoRangeStaticProducer):
@defer.inlineCallbacks
def resumeProducing(self):
if not self.request:
return
data = yield defer.maybeDeferred(self.fileObject.read, self.bufferSize)
if not self.request:
return
if data:
# this .write will spin the reactor, calling .doWrite and then
# .resumeProducing again, so be prepared for a re-entrant call
@@ -27,35 +23,36 @@ class NoRangeStaticProducer(static.NoRangeStaticProducer):
self.request.finish()
self.stopProducing()
class SingleRangeStaticProducer(static.SingleRangeStaticProducer):
@defer.inlineCallbacks
def resumeProducing(self):
if not self.request:
return
data = yield defer.maybeDeferred(self.fileObject.read,
min(self.bufferSize, self.size - self.bytesWritten))
data = yield defer.maybeDeferred(self.fileObject.read, min(self.bufferSize, self.size - self.bytesWritten))
if not self.request:
return
if data:
self.bytesWritten += len(data)
# this .write will spin the reactor, calling .doWrite and then
# .resumeProducing again, so be prepared for a re-entrant call
self.request.write(data)
if self.request and self.bytesWritten == self.size:
self.request.unregisterProducer()
self.request.finish()
self.stopProducing()
class MultipleRangeStaticProducer(static.MultipleRangeStaticProducer):
@defer.inlineCallbacks
def resumeProducing(self):
if not self.request:
return
data = []
dataLength = 0
done = False
@@ -64,9 +61,7 @@ class MultipleRangeStaticProducer(static.MultipleRangeStaticProducer):
dataLength += len(self.partBoundary)
data.append(self.partBoundary)
self.partBoundary = None
p = yield defer.maybeDeferred(self.fileObject.read,
min(self.bufferSize - dataLength,
self._partSize - self._partBytesWritten))
p = yield defer.maybeDeferred(self.fileObject.read, min(self.bufferSize - dataLength, self._partSize - self._partBytesWritten))
self._partBytesWritten += len(p)
dataLength += len(p)
data.append(p)
@@ -76,18 +71,19 @@ class MultipleRangeStaticProducer(static.MultipleRangeStaticProducer):
except StopIteration:
done = True
break
if not self.request:
return
self.request.write(''.join(data))
if done:
self.request.unregisterProducer()
self.request.finish()
self.stopProducing()
self.request = None
class FilelikeObjectResource(static.File):
isLeaf = True
contentType = None

View File

@@ -37,7 +37,6 @@
# statement from all source files in the program, then also delete it here.
#
import json
import gtk
import os
import subprocess
@@ -50,8 +49,7 @@ from deluge.plugins.pluginbase import GtkPluginBase
import deluge.component as component
import deluge.common
from twisted.internet import reactor, defer, threads
from twisted.web import server, resource
from twisted.internet import defer, threads
from common import get_resource
@@ -69,6 +67,13 @@ def execute_url(url):
class GtkUI(GtkPluginBase):
def get_widget(self, widget_name):
main_window = component.get("MainWindow")
if hasattr(main_window, 'main_glade'):
return main_window.main_glade.get_widget(widget_name)
else:
return main_window.main_builder.get_object(widget_name)
def enable(self):
self.glade = gtk.glade.XML(get_resource("config.glade"))
@@ -76,7 +81,7 @@ class GtkUI(GtkPluginBase):
component.get("PluginManager").register_hook("on_apply_prefs", self.on_apply_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 = self.get_widget('menu_file_tab')
self.sep = gtk.SeparatorMenuItem()
self.item = gtk.MenuItem(_("_Stream this file"))
@@ -105,7 +110,7 @@ class GtkUI(GtkPluginBase):
component.get("PluginManager").deregister_hook("on_apply_prefs", self.on_apply_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 = self.get_widget('menu_file_tab')
file_menu.remove(self.item)
file_menu.remove(self.sep)

View File

@@ -1,22 +1,22 @@
from twisted.web.resource import Resource as TwistedResource, _computeAllowedMethods
from twisted.web import server, error
from twisted.web import server
from twisted.internet import defer
class Resource(TwistedResource):
content_type = 'application/json'
def __init__(self, username=None, password=None, *args, **kwargs):
self.username = username
self.password = password
TwistedResource.__init__(self, *args, **kwargs)
def render(self, request): # Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
def render(self, request): # Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
"""
Adds support for deferred render methods
"""
auth_header = request.getHeader('Authorization')
if self.username or self.password:
authenticated = False
if auth_header:
@@ -27,14 +27,13 @@ class Resource(TwistedResource):
username, password = userpass
if self.username == username and self.password == password:
authenticated = True
if not authenticated:
print auth_header
print self.username, self.password
request.setResponseCode(401)
return 'Unauthorized'
m = getattr(self, 'render_' + request.method, None)
if not m:
# This needs to be here until the deprecated subclasses of the
@@ -43,18 +42,18 @@ class Resource(TwistedResource):
allowedMethods = (getattr(self, 'allowedMethods', 0) or
_computeAllowedMethods(self))
raise UnsupportedMethod(allowedMethods)
result = defer.maybeDeferred(m, request)
def write_rest(defer_result, request):
request.write(defer_result)
request.finish()
def err_rest(defer_result=None):
defer_result.printTraceback()
request.finish()
result.addCallback(write_rest, request)
result.addErrback(err_rest)
return server.NOT_DONE_YET
return server.NOT_DONE_YET

View File

@@ -44,6 +44,6 @@ from deluge.plugins.pluginbase import WebPluginBase
from common import get_resource
class WebUI(WebPluginBase):
scripts = [get_resource("streaming.js")]
class WebUI(WebPluginBase):
scripts = [get_resource("streaming.js")]