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

2
.gitignore vendored
View File

@@ -24,7 +24,7 @@ _trial_temp
*.komodoproject
docs/_build*
.env*
EGG-INFO
# for bundling
thomas

14
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
ln -s .env-egg/lib/python2.7/site-packages/thomas .
ln -s .env-egg/lib/python2.7/site-packages/rarfile.py .
ln -s .env-egg/lib/python2.7/site-packages/six.py .
ln -s .env-egg/lib/python2.7/site-packages/rfc6266.py .
ln -s .env-egg/lib/python2.7/site-packages/lepl .
ln -s .env-egg/lib/python2.7/site-packages/pytz .
ln -s .env-egg/lib/python*/site-packages/thomas .
ln -s .env-egg/lib/python*/site-packages/rarfile.py .
ln -s .env-egg/lib/python*/site-packages/six.py .
ln -s .env-egg/lib/python*/site-packages/rfc6266.py .
ln -s .env-egg/lib/python*/site-packages/lepl .
ln -s .env-egg/lib/python*/site-packages/pytz .
.env-egg/bin/python setup.py bdist_egg

View File

@@ -1,3 +1,5 @@
import argparse
import requests
class FailedToStreamException(Exception):
@@ -44,6 +46,8 @@ def stream_torrent(remote_control_url, infohash=None, path=None, wait_for_end_pi
data = r.json()
if data['status'] == 'success':
return data['url']
else:
raise FailedToStreamException('Request failed: %r' % (data, ))
if 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()
if data['status'] == 'success':
return data['url']
else:
raise FailedToStreamException('Request failed: %r' % (data, ))
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 = [
'six',
'rarfile',
'rfc6266',
]
@@ -96,7 +95,9 @@ setup(
%s = %s:CorePlugin
[deluge.plugin.gtkui]
%s = %s:GtkUIPlugin
[deluge.plugin.gtk3ui]
%s = %s:Gtk3UIPlugin
[deluge.plugin.web]
%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):
def __init__(self, plugin_name):
from core import Core as _plugin_cls
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
from .gtkui import GtkUI as _plugin_cls
self._plugin_cls = _plugin_cls
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):
def __init__(self, plugin_name):
from webui import WebUI as _plugin_cls
from .webui import WebUI as _plugin_cls
self._plugin_cls = _plugin_cls
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.
#
import base64
import json
import logging
import os
@@ -57,6 +58,7 @@ from deluge.plugins.pluginbase import CorePluginBase
from twisted.internet import reactor, defer, task
from twisted.web import server, client
from twisted.web.resource import Resource as TwistedResource
from thomas import router, Item, OutputBase
@@ -94,6 +96,7 @@ DEFAULT_PREFS = {
'ssl_source': 'daemon',
'ssl_priv_key_path': '',
'ssl_cert_path': '',
'aggressive_prioritizing': False,
}
logger = logging.getLogger(__name__)
@@ -126,9 +129,10 @@ def get_torrent(infohash):
class Torrent(object):
def __init__(self, torrent_handler, infohash):
def __init__(self, torrent_handler, infohash, aggressive_prioritizing=False):
self.torrent_handler = torrent_handler
self.infohash = infohash
self.aggressive_prioritizing = aggressive_prioritizing
self.filesets = {}
self.readers = {}
@@ -176,7 +180,7 @@ class Torrent(object):
if file_piece_count <= MIN_PIECE_COUNT_FOR_CHAIN_CONSIDERATION:
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_piece = best_reader_from_byte // self.piece_length
downloading_pieces = self.get_currently_downloading()
@@ -188,9 +192,11 @@ class Torrent(object):
piece_diff = best_reader_piece - unfinished_piece - 1
if unfinished_piece >= best_reader_piece or piece_diff / file_piece_count <= WITHIN_CHAIN_PERCENTAGE:
is_next_in_chain = True
else:
is_next_in_chain = True
if not is_next_in_chain:
logger.debug('Not a next-in-chain piece, setting priority now')
if not is_next_in_chain or self.aggressive_prioritizing:
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.piece_priority(needed_piece, MAX_PIECE_PRIORITY)
@@ -383,9 +389,10 @@ class Torrent(object):
class TorrentHandler(object):
def __init__(self, reset_priorities_on_finish):
def __init__(self, reset_priorities_on_finish, aggressive_prioritizing=False):
self.torrents = {}
self.reset_priorities_on_finish = reset_priorities_on_finish
self.aggressive_prioritizing = aggressive_prioritizing
self.alerts = component.get("AlertManager")
self.alerts.register_handler("torrent_removed_alert", self.on_alert_torrent_removed)
@@ -488,7 +495,7 @@ class TorrentHandler(object):
def get_torrent(self, infohash):
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]
@defer.inlineCallbacks
@@ -612,58 +619,61 @@ class StreamResource(Resource):
@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'))
label = request.args.get('label')
infohash = request.args.get(b'infohash')
path = request.args.get(b'path')
wait_for_end_pieces = bool(request.args.get(b'wait_for_end_pieces'))
label = request.args.get(b'label')
if path:
path = path[0]
path = path[0].decode('utf-8')
else:
path = None
if infohash:
infohash = infohash[0]
infohash = infohash[0].decode('utf-8')
else:
infohash = infohash
infohash = None
if label:
label = label[0]
label = label[0].decode('utf-8')
else:
label = None
payload = request.content.read()
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)
defer.returnValue(json.dumps(result))
defer.returnValue(json.dumps(result).encode('utf-8'))
@defer.inlineCallbacks
def render_GET(self, request):
infohash = request.args.get('infohash')
path = request.args.get('path')
wait_for_end_pieces = bool(request.args.get('wait_for_end_pieces'))
infohash = request.args.get(b'infohash')
path = request.args.get(b'path')
wait_for_end_pieces = bool(request.args.get(b'wait_for_end_pieces'))
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:
path = path[0]
path = path[0].decode('utf-8')
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).encode('utf-8'))
class Core(CorePluginBase):
listening = None
base_url = None
_is_enabled = False
def enable(self):
self._is_enabled = True
self.config = deluge.configmanager.ConfigManager("streaming.conf", DEFAULT_PREFS)
try:
@@ -680,18 +690,18 @@ class Core(CorePluginBase):
self.thomas_http_output = http_output
resource = Resource()
resource.putChild('file', http_output.resource)
resource = TwistedResource()
resource.putChild(b'file', http_output.resource)
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'],
client=self))
base_resource = Resource()
base_resource.putChild('streaming', resource)
base_resource = TwistedResource()
base_resource.putChild(b'streaming', 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")
logger.warning('plugins %s' % (plugin_manager.get_enabled_plugins(), ))
@@ -748,6 +758,11 @@ class Core(CorePluginBase):
@defer.inlineCallbacks
def disable(self):
if not self._is_enabled:
defer.returnValue(None)
self._is_enabled = False
self.site.stopFactory()
self.torrent_handler.shutdown()
self.thomas_http_output.stop()
@@ -828,7 +843,7 @@ class Core(CorePluginBase):
core = component.get("Core")
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():
label_plugin = component.get('CorePlugin.Label')
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',
style: 'margin-left: 12px;'
}));
om.bind('aggressive_prioritizing', fieldset.add({
xtype: 'checkbox',
name: 'aggressive_prioritizing',
boxLabel: 'Aggressive prioritizing',
style: 'margin-left: 12px;'
}));
},
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:
subprocess.Popen(['xdg-open', url])
except OSError:
print 'Unable to open URL %s' % (url, )
print('Unable to open URL %s' % (url, ))
class GtkUI(GtkPluginBase):

View File

@@ -1,3 +1,5 @@
import base64
from twisted.web.resource import Resource as TwistedResource, _computeAllowedMethods
from twisted.web import server
from twisted.internet import defer
@@ -22,7 +24,7 @@ class Resource(TwistedResource):
if auth_header:
auth_header = auth_header.split(' ')
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:
username, password = userpass
if self.username == username and self.password == password:
@@ -32,7 +34,7 @@ class Resource(TwistedResource):
request.setResponseCode(401)
return 'Unauthorized'
m = getattr(self, 'render_' + request.method, None)
m = getattr(self, 'render_' + request.method.decode('utf-8'), None)
if not m:
# This needs to be here until the deprecated subclasses of the
# 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:
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()
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
if self._open_file:

View File

@@ -36,14 +36,20 @@
# 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
from deluge.log import LOG as log
from deluge.ui.client import client
from deluge import component
from deluge.plugins.pluginbase import WebPluginBase
from common import get_resource
from .common import get_resource
log = logging.getLogger(__name__)
class WebUI(WebPluginBase):
scripts = [get_resource("streaming.js")]
def enable(self):
pass
def disable(self):
pass