Initial Deluge v2 #22 support and functionality to hopefully fix #23

This commit is contained in:
Anders Jensen
2019-11-02 21:55:23 +01:00
parent 934048dd46
commit d058d1d914
13 changed files with 1077 additions and 54 deletions

4
.gitignore vendored
View File

@@ -24,7 +24,7 @@ _trial_temp
*.komodoproject *.komodoproject
docs/_build* docs/_build*
.env* .env*
EGG-INFO
# for bundling # for bundling
thomas thomas
@@ -32,4 +32,4 @@ six.py
rarfile.py rarfile.py
rfc6266.py rfc6266.py
lepl lepl
pytz pytz

16
create-egg.sh Normal file → Executable file
View File

@@ -1,9 +1,9 @@
virtualenv .env-egg python3 -m venv .env-egg
.env-egg/bin/pip install -U thomas .env-egg/bin/pip install -U thomas
ln -s .env-egg/lib/python2.7/site-packages/thomas . ln -s .env-egg/lib/python*/site-packages/thomas .
ln -s .env-egg/lib/python2.7/site-packages/rarfile.py . ln -s .env-egg/lib/python*/site-packages/rarfile.py .
ln -s .env-egg/lib/python2.7/site-packages/six.py . ln -s .env-egg/lib/python*/site-packages/six.py .
ln -s .env-egg/lib/python2.7/site-packages/rfc6266.py . ln -s .env-egg/lib/python*/site-packages/rfc6266.py .
ln -s .env-egg/lib/python2.7/site-packages/lepl . ln -s .env-egg/lib/python*/site-packages/lepl .
ln -s .env-egg/lib/python2.7/site-packages/pytz . ln -s .env-egg/lib/python*/site-packages/pytz .
.env-egg/bin/python setup.py bdist_egg .env-egg/bin/python setup.py bdist_egg

View File

@@ -1,3 +1,5 @@
import argparse
import requests import requests
class FailedToStreamException(Exception): class FailedToStreamException(Exception):
@@ -44,6 +46,8 @@ def stream_torrent(remote_control_url, infohash=None, path=None, wait_for_end_pi
data = r.json() data = r.json()
if data['status'] == 'success': if data['status'] == 'success':
return data['url'] return data['url']
else:
raise FailedToStreamException('Request failed: %r' % (data, ))
if torrent_body: if torrent_body:
r = requests.post(url, auth=(username, password), params=params, data=torrent_body) r = requests.post(url, auth=(username, password), params=params, data=torrent_body)
@@ -53,5 +57,40 @@ def stream_torrent(remote_control_url, infohash=None, path=None, wait_for_end_pi
data = r.json() data = r.json()
if data['status'] == 'success': if data['status'] == 'success':
return data['url'] return data['url']
else:
raise FailedToStreamException('Request failed: %r' % (data, ))
raise FailedToStreamException('Streaming was never successful') raise FailedToStreamException('Streaming was never successful')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Stream some torrents")
parser.add_argument('url', help="Full API Url including auth info")
parser.add_argument('--infohash', nargs='?', help="Infohash of torrent to stream")
parser.add_argument('--path', nargs='?', help="Path to file within the torrent to stream")
parser.add_argument('--label', nargs='?', help="Label to add the torrent with")
parser.add_argument('--torrent', nargs='?', help="Path to the torrent to stream", type=argparse.FileType(mode='rb'))
parser.add_argument('--skip_wait_for_end_pieces', help="Wait until client downloaded the first and last piece of the torrent", action='store_false')
args = parser.parse_args()
kwargs = {
'remote_control_url': args.url,
'wait_for_end_pieces': args.skip_wait_for_end_pieces
}
if args.infohash:
kwargs['infohash'] = args.infohash
if args.path:
kwargs['path'] = args.path
if args.label:
kwargs['label'] = args.label
if args.torrent:
kwargs['torrent_body'] = args.torrent.read()
result = stream_torrent(**kwargs)
print('URL %s' % (result, ))

View File

@@ -71,7 +71,6 @@ REQUIREMENTS_PACKAGES = [
] ]
REQUIREMENTS_MODULES = [ REQUIREMENTS_MODULES = [
'six',
'rarfile', 'rarfile',
'rfc6266', 'rfc6266',
] ]
@@ -96,7 +95,9 @@ setup(
%s = %s:CorePlugin %s = %s:CorePlugin
[deluge.plugin.gtkui] [deluge.plugin.gtkui]
%s = %s:GtkUIPlugin %s = %s:GtkUIPlugin
[deluge.plugin.gtk3ui]
%s = %s:Gtk3UIPlugin
[deluge.plugin.web] [deluge.plugin.web]
%s = %s:WebUIPlugin %s = %s:WebUIPlugin
""" % ((__plugin_name__, __plugin_name__.lower())*3) """ % ((__plugin_name__, __plugin_name__.lower())*4)
) )

View File

@@ -42,20 +42,27 @@ from deluge.plugins.init import PluginInitBase
class CorePlugin(PluginInitBase): class CorePlugin(PluginInitBase):
def __init__(self, plugin_name): def __init__(self, plugin_name):
from core import Core as _plugin_cls from .core import Core as _plugin_cls
self._plugin_cls = _plugin_cls self._plugin_cls = _plugin_cls
super(CorePlugin, self).__init__(plugin_name) super(CorePlugin, self).__init__(plugin_name)
class GtkUIPlugin(PluginInitBase): class GtkUIPlugin(PluginInitBase):
def __init__(self, plugin_name): def __init__(self, plugin_name):
from gtkui import GtkUI as _plugin_cls from .gtkui import GtkUI as _plugin_cls
self._plugin_cls = _plugin_cls self._plugin_cls = _plugin_cls
super(GtkUIPlugin, self).__init__(plugin_name) super(GtkUIPlugin, self).__init__(plugin_name)
class Gtk3UIPlugin(PluginInitBase):
def __init__(self, plugin_name):
from .gtk3ui import Gtk3UI as PluginClass
self._plugin_cls = PluginClass
super(Gtk3UIPlugin, self).__init__(plugin_name)
class WebUIPlugin(PluginInitBase): class WebUIPlugin(PluginInitBase):
def __init__(self, plugin_name): def __init__(self, plugin_name):
from webui import WebUI as _plugin_cls from .webui import WebUI as _plugin_cls
self._plugin_cls = _plugin_cls self._plugin_cls = _plugin_cls
super(WebUIPlugin, self).__init__(plugin_name) super(WebUIPlugin, self).__init__(plugin_name)

View File

@@ -37,6 +37,7 @@
# statement from all source files in the program, then also delete it here. # statement from all source files in the program, then also delete it here.
# #
import base64
import json import json
import logging import logging
import os import os
@@ -57,6 +58,7 @@ from deluge.plugins.pluginbase import CorePluginBase
from twisted.internet import reactor, defer, task from twisted.internet import reactor, defer, task
from twisted.web import server, client from twisted.web import server, client
from twisted.web.resource import Resource as TwistedResource
from thomas import router, Item, OutputBase from thomas import router, Item, OutputBase
@@ -94,6 +96,7 @@ DEFAULT_PREFS = {
'ssl_source': 'daemon', 'ssl_source': 'daemon',
'ssl_priv_key_path': '', 'ssl_priv_key_path': '',
'ssl_cert_path': '', 'ssl_cert_path': '',
'aggressive_prioritizing': False,
} }
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -126,9 +129,10 @@ def get_torrent(infohash):
class Torrent(object): class Torrent(object):
def __init__(self, torrent_handler, infohash): def __init__(self, torrent_handler, infohash, aggressive_prioritizing=False):
self.torrent_handler = torrent_handler self.torrent_handler = torrent_handler
self.infohash = infohash self.infohash = infohash
self.aggressive_prioritizing = aggressive_prioritizing
self.filesets = {} self.filesets = {}
self.readers = {} self.readers = {}
@@ -176,7 +180,7 @@ class Torrent(object):
if file_piece_count <= MIN_PIECE_COUNT_FOR_CHAIN_CONSIDERATION: if file_piece_count <= MIN_PIECE_COUNT_FOR_CHAIN_CONSIDERATION:
is_next_in_chain = True is_next_in_chain = True
else: elif self.readers:
best_reader_from_byte = max(reader[1] for reader in self.readers.values() if reader[1] <= from_byte) best_reader_from_byte = max(reader[1] for reader in self.readers.values() if reader[1] <= from_byte)
best_reader_piece = best_reader_from_byte // self.piece_length best_reader_piece = best_reader_from_byte // self.piece_length
downloading_pieces = self.get_currently_downloading() downloading_pieces = self.get_currently_downloading()
@@ -188,9 +192,11 @@ class Torrent(object):
piece_diff = best_reader_piece - unfinished_piece - 1 piece_diff = best_reader_piece - unfinished_piece - 1
if unfinished_piece >= best_reader_piece or piece_diff / file_piece_count <= WITHIN_CHAIN_PERCENTAGE: if unfinished_piece >= best_reader_piece or piece_diff / file_piece_count <= WITHIN_CHAIN_PERCENTAGE:
is_next_in_chain = True is_next_in_chain = True
else:
is_next_in_chain = True
if not is_next_in_chain: if not is_next_in_chain or self.aggressive_prioritizing:
logger.debug('Not a next-in-chain piece, setting priority now') logger.debug('Not a next-in-chain piece or aggressive prioritization enabled, setting priority now')
self.torrent.handle.set_piece_deadline(needed_piece, 0) self.torrent.handle.set_piece_deadline(needed_piece, 0)
self.torrent.handle.piece_priority(needed_piece, MAX_PIECE_PRIORITY) self.torrent.handle.piece_priority(needed_piece, MAX_PIECE_PRIORITY)
@@ -383,9 +389,10 @@ class Torrent(object):
class TorrentHandler(object): class TorrentHandler(object):
def __init__(self, reset_priorities_on_finish): def __init__(self, reset_priorities_on_finish, aggressive_prioritizing=False):
self.torrents = {} self.torrents = {}
self.reset_priorities_on_finish = reset_priorities_on_finish self.reset_priorities_on_finish = reset_priorities_on_finish
self.aggressive_prioritizing = aggressive_prioritizing
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)
@@ -488,7 +495,7 @@ class TorrentHandler(object):
def get_torrent(self, infohash): def get_torrent(self, infohash):
if infohash not in self.torrents: if infohash not in self.torrents:
self.torrents[infohash] = Torrent(self, infohash) self.torrents[infohash] = Torrent(self, infohash, self.aggressive_prioritizing)
return self.torrents[infohash] return self.torrents[infohash]
@defer.inlineCallbacks @defer.inlineCallbacks
@@ -612,58 +619,61 @@ class StreamResource(Resource):
@defer.inlineCallbacks @defer.inlineCallbacks
def render_POST(self, request): def render_POST(self, request):
infohash = request.args.get('infohash') infohash = request.args.get(b'infohash')
path = request.args.get('path') path = request.args.get(b'path')
wait_for_end_pieces = bool(request.args.get('wait_for_end_pieces')) wait_for_end_pieces = bool(request.args.get(b'wait_for_end_pieces'))
label = request.args.get('label') label = request.args.get(b'label')
if path: if path:
path = path[0] path = path[0].decode('utf-8')
else: else:
path = None path = None
if infohash: if infohash:
infohash = infohash[0] infohash = infohash[0].decode('utf-8')
else: else:
infohash = infohash infohash = None
if label: if label:
label = label[0] label = label[0].decode('utf-8')
else: else:
label = None label = None
payload = request.content.read() payload = request.content.read()
if not payload: if not payload:
defer.returnValue(json.dumps({'status': 'error', 'message': 'invalid torrent'})) defer.returnValue(json.dumps({'status': 'error', 'message': 'invalid torrent'}).encode('utf-8'))
result = yield self.client.stream_torrent(infohash=infohash, filedump=payload, filepath_or_index=path, wait_for_end_pieces=wait_for_end_pieces, label=label) result = yield self.client.stream_torrent(infohash=infohash, filedump=payload, filepath_or_index=path, wait_for_end_pieces=wait_for_end_pieces, label=label)
defer.returnValue(json.dumps(result)) defer.returnValue(json.dumps(result).encode('utf-8'))
@defer.inlineCallbacks @defer.inlineCallbacks
def render_GET(self, request): def render_GET(self, request):
infohash = request.args.get('infohash') infohash = request.args.get(b'infohash')
path = request.args.get('path') path = request.args.get(b'path')
wait_for_end_pieces = bool(request.args.get('wait_for_end_pieces')) wait_for_end_pieces = bool(request.args.get(b'wait_for_end_pieces'))
if not infohash: if not infohash:
defer.returnValue(json.dumps({'status': 'error', 'message': 'missing infohash'})) defer.returnValue(json.dumps({'status': 'error', 'message': 'missing infohash'}).encode('utf-8'))
infohash = infohash[0] infohash = infohash[0].decode('utf-8')
if path: if path:
path = path[0] path = path[0].decode('utf-8')
else: else:
path = None path = None
result = yield self.client.stream_torrent(infohash=infohash, filepath_or_index=path, wait_for_end_pieces=wait_for_end_pieces) 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).encode('utf-8'))
class Core(CorePluginBase): class Core(CorePluginBase):
listening = None listening = None
base_url = None base_url = None
_is_enabled = False
def enable(self): def enable(self):
self._is_enabled = True
self.config = deluge.configmanager.ConfigManager("streaming.conf", DEFAULT_PREFS) self.config = deluge.configmanager.ConfigManager("streaming.conf", DEFAULT_PREFS)
try: try:
@@ -680,18 +690,18 @@ class Core(CorePluginBase):
self.thomas_http_output = http_output self.thomas_http_output = http_output
resource = Resource() resource = TwistedResource()
resource.putChild('file', http_output.resource) resource.putChild(b'file', http_output.resource)
if self.config['allow_remote']: if self.config['allow_remote']:
resource.putChild('stream', StreamResource(username=self.config['remote_username'], resource.putChild(b'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 = TwistedResource()
base_resource.putChild('streaming', resource) base_resource.putChild(b'streaming', resource)
self.site = server.Site(base_resource) self.site = server.Site(base_resource)
self.torrent_handler = TorrentHandler(self.config['download_only_streamed'] == False) self.torrent_handler = TorrentHandler(self.config['download_only_streamed'] == False, self.config['aggressive_prioritizing'])
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(), ))
@@ -748,6 +758,11 @@ class Core(CorePluginBase):
@defer.inlineCallbacks @defer.inlineCallbacks
def disable(self): def disable(self):
if not self._is_enabled:
defer.returnValue(None)
self._is_enabled = False
self.site.stopFactory() self.site.stopFactory()
self.torrent_handler.shutdown() self.torrent_handler.shutdown()
self.thomas_http_output.stop() self.thomas_http_output.stop()
@@ -828,7 +843,7 @@ class Core(CorePluginBase):
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', base64.b64encode(filedump), {'add_paused': True})
if label and 'Label' in component.get('CorePluginManager').get_enabled_plugins(): if label and 'Label' in component.get('CorePluginManager').get_enabled_plugins():
label_plugin = component.get('CorePlugin.Label') label_plugin = component.get('CorePlugin.Label')
if label not in label_plugin.get_labels(): if label not in label_plugin.get_labels():

712
streaming/data/config.ui Normal file
View File

@@ -0,0 +1,712 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.16 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkWindow" id="window1">
<property name="can_focus">False</property>
<child>
<object class="GtkVBox" id="prefs_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkFrame" id="settings_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="settings_alignment">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">10</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="settings_vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkVBox" id="settings_vbox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkCheckButton" id="input_download_only_streamed">
<property name="label" translatable="yes">Download only streamed files, skip the other files</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="settings_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="serving_frame">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="settings_alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">10</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="settings_vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="remote_username_label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Hostname: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_ip">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="remote_username_label3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Port: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox54">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkCheckButton" id="input_reverse_proxy_enabled">
<property name="label" translatable="yes">Enable Reverse Proxy</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox55">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="reverse_proxy_base_url_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Reverse Proxy Base Url: </property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_reverse_proxy_base_url">
<property name="visible">True</property>
<property name="can_focus">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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<!-- <child>
<widget class="GtkRadioButton" id="input_serve_webui">
<property name="label" translatable="yes">Serve files via WebUI</property>
<property name="visible">False</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</widget>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child> -->
<child>
<object class="GtkVBox" id="settings_vbox3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<!-- <child>
<widget class="GtkRadioButton" id="input_serve_standalone">
<property name="label" translatable="yes">Serve files via standalone</property>
<property name="visible">False</property>
<property name="sensitive">False</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">input_serve_webui</property>
</widget>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> -->
<child>
<object class="GtkAlignment" id="remote_alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<!-- <property name="left_padding">20</property> -->
<child>
<object class="GtkVBox" id="remote_vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkCheckButton" id="input_use_ssl">
<property name="label" translatable="yes">Use SSL</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">20</property>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkRadioButton" id="input_ssl_cert_daemon">
<property name="label" translatable="yes">Use Daemon/WebUI Certificate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="input_ssl_cert_custom">
<property name="label" translatable="yes">Custom Certificate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="active">True</property>
<property name="draw_indicator">True</property>
<property name="group">input_ssl_cert_daemon</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">20</property>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Private key file path</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_ssl_priv_key_path">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Certificate and chains file path</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_ssl_cert_path">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">5</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="serving_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;File Serving Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkFrame" id="settings_frame1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label_xalign">0</property>
<property name="shadow_type">none</property>
<child>
<object class="GtkAlignment" id="settings_alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="top_padding">10</property>
<property name="left_padding">12</property>
<child>
<object class="GtkVBox" id="settings_vbox4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkVBox" id="settings_vbox5">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkCheckButton" id="input_allow_remote">
<property name="label" translatable="yes">Allow remote control</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkAlignment" id="remote_alignment2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="left_padding">32</property>
<child>
<object class="GtkVBox" id="remote_vbox2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<!-- <child>
<widget class="GtkHBox" id="remote_username_hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<widget class="GtkLabel" id="remote_username_label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remote control username:</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="input_remote_username">
<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>
</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">0</property>
</packing>
</child> -->
<child>
<object class="GtkHBox" id="remote_password_hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object class="GtkLabel" id="remote_password_label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Remote control password:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="input_remote_password">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="visibility">True</property>
<property name="invisible_char">&#x2022;</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="remote_url_hbox1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="spacing">5</property>
<child>
<object 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>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="output_remote_url">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</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>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="input_use_stream_urls">
<property name="label" translatable="yes">Use stream protocol urls</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="input_auto_open_stream_urls">
<property name="label" translatable="yes">Auto-open stream protocol urls</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="input_aggressive_prioritizing">
<property name="label" translatable="yes">Aggressive prioritizing</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_action_appearance">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">3</property>
</packing>
</child>
</object>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel" id="settings_label1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;Advanced Settings&lt;/b&gt;</property>
<property name="use_markup">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@@ -270,6 +270,13 @@ PreferencePage = Ext.extend(Ext.Panel, {
boxLabel: 'Auto-open stream protocol urls', boxLabel: 'Auto-open stream protocol urls',
style: 'margin-left: 12px;' style: 'margin-left: 12px;'
})); }));
om.bind('aggressive_prioritizing', fieldset.add({
xtype: 'checkbox',
name: 'aggressive_prioritizing',
boxLabel: 'Aggressive prioritizing',
style: 'margin-left: 12px;'
}));
}, },
onApply: function() { onApply: function() {

234
streaming/gtk3ui.py Normal file
View File

@@ -0,0 +1,234 @@
#
# gtkui.py
#
# Copyright (C) 2009 John Doee <johndoee@tidalstream.org>
#
# Basic plugin template created by:
# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
import logging
import os
import sys
import subprocess
from gi.repository import Gtk
from gi.repository.Gtk import Menu, MenuItem, SeparatorMenuItem
import deluge.component as component
from deluge.plugins.pluginbase import Gtk3PluginBase
from deluge.ui.client import client
from deluge.ui.gtk3 import dialogs
from twisted.internet import defer, threads
from .common import get_resource
log = logging.getLogger(__name__)
def execute_url(url):
if sys.platform == 'win32':
os.startfile(url)
elif sys.platform == 'darwin':
subprocess.Popen(['open', url])
else:
try:
subprocess.Popen(['xdg-open', url])
except OSError:
print('Unable to open URL %s' % (url, ))
class Gtk3UI(Gtk3PluginBase):
def enable(self):
self.builder = Gtk.Builder()
self.builder.add_from_file(get_resource('config.ui'))
component.get('Preferences').add_page('Streaming', self.builder.get_object('prefs_box'))
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 = self.get_widget('menu_file_tab')
self.sep = SeparatorMenuItem()
self.item = MenuItem(_("Stream this file"))
self.item.connect("activate", self.on_menuitem_stream)
file_menu.append(self.sep)
file_menu.append(self.item)
self.sep.show()
self.item.show()
torrentmenu = component.get("MenuBar").torrentmenu
self.sep_torrentmenu = SeparatorMenuItem()
self.item_torrentmenu = MenuItem(_("Stream this torrent"))
self.item_torrentmenu.connect("activate", self.on_torrentmenu_menuitem_stream)
torrentmenu.append(self.sep_torrentmenu)
torrentmenu.append(self.item_torrentmenu)
self.sep_torrentmenu.show()
self.item_torrentmenu.show()
def disable(self):
component.get('Preferences').remove_page('Streaming')
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 = self.get_widget('menu_file_tab')
file_menu.remove(self.item)
file_menu.remove(self.sep)
torrentmenu = component.get("MenuBar").torrentmenu
torrentmenu.remove(self.item_torrentmenu)
torrentmenu.remove(self.sep_torrentmenu)
def get_widget(self, widget_name):
main_builder = component.get('MainWindow').get_builder()
return main_builder.get_object(widget_name)
@defer.inlineCallbacks
def on_apply_prefs(self):
log.debug("applying prefs for Streaming")
serve_method = 'standalone'
# if self.builder.get_object("input_serve_standalone").get_active():
# serve_method = 'standalone'
# elif self.builder.get_object("input_serve_webui").get_active():
# serve_method = 'webui'
if self.builder.get_object("input_ssl_cert_daemon").get_active():
ssl_source = 'daemon'
elif self.builder.get_object("input_ssl_cert_custom").get_active():
ssl_source = 'custom'
config = {
"ip": self.builder.get_object("input_ip").get_text(),
"port": int(self.builder.get_object("input_port").get_text()),
"use_stream_urls": self.builder.get_object("input_use_stream_urls").get_active(),
"auto_open_stream_urls": self.builder.get_object("input_auto_open_stream_urls").get_active(),
"aggressive_prioritizing": self.builder.get_object("input_aggressive_prioritizing").get_active(),
"allow_remote": self.builder.get_object("input_allow_remote").get_active(),
"download_only_streamed": self.builder.get_object("input_download_only_streamed").get_active(),
"reverse_proxy_enabled": self.builder.get_object("input_reverse_proxy_enabled").get_active(),
# "download_in_order": self.builder.get_object("input_download_in_order").get_active(),
"use_ssl": self.builder.get_object("input_use_ssl").get_active(),
# "remote_username": self.builder.get_object("input_remote_username").get_text(),
"reverse_proxy_base_url": self.builder.get_object("input_reverse_proxy_base_url").get_text(),
"remote_password": self.builder.get_object("input_remote_password").get_text(),
"ssl_priv_key_path": self.builder.get_object("input_ssl_priv_key_path").get_text(),
"ssl_cert_path": self.builder.get_object("input_ssl_cert_path").get_text(),
"serve_method": serve_method,
"ssl_source": ssl_source,
}
result = yield client.streaming.set_config(config)
if result:
message_type, message_class, message = result
if message_type == 'error':
topic = 'Unknown error type'
if message_class == 'ssl':
topic = 'SSL Failed'
dialogs.ErrorDialog(topic, message).run()
def on_show_prefs(self):
client.streaming.get_config().addCallback(self.cb_get_config)
def cb_get_config(self, config):
"""callback for on show_prefs"""
self.builder.get_object("input_ip").set_text(config["ip"])
self.builder.get_object("input_port").set_text(str(config["port"]))
self.builder.get_object("input_use_stream_urls").set_active(config["use_stream_urls"])
self.builder.get_object("input_auto_open_stream_urls").set_active(config["auto_open_stream_urls"])
self.builder.get_object("input_aggressive_prioritizing").set_active(config["aggressive_prioritizing"])
self.builder.get_object("input_allow_remote").set_active(config["allow_remote"])
self.builder.get_object("input_use_ssl").set_active(config["use_ssl"])
self.builder.get_object("input_download_only_streamed").set_active(config["download_only_streamed"])
self.builder.get_object("input_reverse_proxy_enabled").set_active(config["reverse_proxy_enabled"])
# self.builder.get_object("input_download_in_order").set_active(config["download_in_order"])
# self.builder.get_object("input_download_everything").set_active(not config["download_in_order"] and not config["download_only_streamed"])
# self.builder.get_object("input_remote_username").set_text(config["remote_username"])
self.builder.get_object("input_reverse_proxy_base_url").set_text(config["reverse_proxy_base_url"])
self.builder.get_object("input_remote_password").set_text(config["remote_password"])
self.builder.get_object("input_ssl_priv_key_path").set_text(config["ssl_priv_key_path"])
self.builder.get_object("input_ssl_cert_path").set_text(config["ssl_cert_path"])
# self.builder.get_object("input_serve_standalone").set_active(config["serve_method"] == "standalone")
# self.builder.get_object("input_serve_webui").set_active(config["serve_method"] == "webui")
self.builder.get_object("input_ssl_cert_daemon").set_active(config["ssl_source"] == "daemon")
self.builder.get_object("input_ssl_cert_custom").set_active(config["ssl_source"] == "custom")
api_url = 'http%s://%s:%s@%s:%s/streaming/stream' % (('s' if config["use_ssl"] else ''), config["remote_username"], config["remote_password"], config["ip"], config["port"])
self.builder.get_object("output_remote_url").set_text(api_url)
def on_torrentmenu_menuitem_stream(self, data=None):
torrent_id = component.get("TorrentView").get_selected_torrents()[0]
client.streaming.stream_torrent(infohash=torrent_id).addCallback(self.stream_ready)
def on_menuitem_stream(self, data=None):
torrent_id = component.get("TorrentView").get_selected_torrents()[0]
ft = component.get("TorrentDetails").tabs['Files']
paths = ft.listview.get_selection().get_selected_rows()[1]
selected = []
for path in paths:
selected.append(ft.treestore.get_iter(path))
for select in selected:
path = ft.get_file_path(select)
client.streaming.stream_torrent(infohash=torrent_id, filepath_or_index=path, includes_name=True).addCallback(self.stream_ready)
break
def stream_ready(self, result):
if result['status'] == 'success':
if result.get('use_stream_urls', False):
url = "stream+%s" % result['url']
if result.get('auto_open_stream_urls', False):
threads.deferToThread(execute_url, url)
else:
def on_dialog_callback(response):
if response == gtk.RESPONSE_YES:
threads.deferToThread(execute_url, url)
dialogs.YesNoDialog('Stream ready', 'Do you want to play the video?').run().addCallback(on_dialog_callback)
else:
dialogs.ErrorDialog('Stream ready', 'Copy the link into a media player', details=result['url']).run()
else:
dialogs.ErrorDialog('Stream failed', 'Was unable to prepare the stream', details=result).run()

View File

@@ -63,7 +63,7 @@ def execute_url(url):
try: try:
subprocess.Popen(['xdg-open', url]) subprocess.Popen(['xdg-open', url])
except OSError: except OSError:
print 'Unable to open URL %s' % (url, ) print('Unable to open URL %s' % (url, ))
class GtkUI(GtkPluginBase): class GtkUI(GtkPluginBase):

View File

@@ -1,3 +1,5 @@
import base64
from twisted.web.resource import Resource as TwistedResource, _computeAllowedMethods from twisted.web.resource import Resource as TwistedResource, _computeAllowedMethods
from twisted.web import server from twisted.web import server
from twisted.internet import defer from twisted.internet import defer
@@ -22,7 +24,7 @@ class Resource(TwistedResource):
if auth_header: if auth_header:
auth_header = auth_header.split(' ') auth_header = auth_header.split(' ')
if len(auth_header) > 1 and auth_header[0] == 'Basic': if len(auth_header) > 1 and auth_header[0] == 'Basic':
userpass = auth_header[1].decode('base64').split(':') userpass = base64.b64decode(auth_header[1].encode('utf-8')).decode('utf-8').split(':')
if len(userpass) == 2: if len(userpass) == 2:
username, password = userpass username, password = userpass
if self.username == username and self.password == password: if self.username == username and self.password == password:
@@ -32,7 +34,7 @@ class Resource(TwistedResource):
request.setResponseCode(401) request.setResponseCode(401)
return 'Unauthorized' return 'Unauthorized'
m = getattr(self, 'render_' + request.method, None) m = getattr(self, 'render_' + request.method.decode('utf-8'), None)
if not m: if not m:
# This needs to be here until the deprecated subclasses of the # This needs to be here until the deprecated subclasses of the
# below three error resources in twisted.web.error are removed. # below three error resources in twisted.web.error are removed.

View File

@@ -45,9 +45,9 @@ class DelugeTorrentInput(InputBase.find_plugin('file')):
if not self._open_file: if not self._open_file:
self.seek(0) self.seek(0)
#logger.debug('Trying to read %s from %i torrentfile_id %r' % (self.path, self.tell(), id(self))) logger.debug('Trying to read %s from %i torrentfile_id %r' % (self.path, self.tell(), id(self)))
tell = self.tell() tell = self.tell()
if self.can_read_to <= tell or self.can_read_to is None: if self.can_read_to is None or self.can_read_to <= tell:
self.can_read_to = self.torrent.can_read(self.offset + tell) + tell self.can_read_to = self.torrent.can_read(self.offset + tell) + tell
if self._open_file: if self._open_file:

View File

@@ -36,14 +36,20 @@
# this exception statement from your version. If you delete this exception # this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here. # statement from all source files in the program, then also delete it here.
# #
import logging
from deluge.log import LOG as log
from deluge.ui.client import client
from deluge import component
from deluge.plugins.pluginbase import WebPluginBase from deluge.plugins.pluginbase import WebPluginBase
from common import get_resource from .common import get_resource
log = logging.getLogger(__name__)
class WebUI(WebPluginBase): class WebUI(WebPluginBase):
scripts = [get_resource("streaming.js")] scripts = [get_resource("streaming.js")]
def enable(self):
pass
def disable(self):
pass