From d058d1d9141a019d8e737a5aba70f50d87b69b9a Mon Sep 17 00:00:00 2001 From: Anders Jensen Date: Sat, 2 Nov 2019 21:55:23 +0100 Subject: [PATCH] Initial Deluge v2 #22 support and functionality to hopefully fix #23 --- .gitignore | 4 +- create-egg.sh | 16 +- examples/http-api/streamtorrent.py | 39 ++ setup.py | 5 +- streaming/__init__.py | 13 +- streaming/core.py | 75 +-- streaming/data/config.ui | 712 +++++++++++++++++++++++++++++ streaming/data/streaming.js | 7 + streaming/gtk3ui.py | 234 ++++++++++ streaming/gtkui.py | 2 +- streaming/resource.py | 6 +- streaming/torrentfile.py | 4 +- streaming/webui.py | 14 +- 13 files changed, 1077 insertions(+), 54 deletions(-) mode change 100644 => 100755 create-egg.sh create mode 100644 streaming/data/config.ui create mode 100644 streaming/gtk3ui.py diff --git a/.gitignore b/.gitignore index 2d7481d..5d4547d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ _trial_temp *.komodoproject docs/_build* .env* - +EGG-INFO # for bundling thomas @@ -32,4 +32,4 @@ six.py rarfile.py rfc6266.py lepl -pytz \ No newline at end of file +pytz diff --git a/create-egg.sh b/create-egg.sh old mode 100644 new mode 100755 index 7d5d2d1..9f4ffc0 --- a/create-egg.sh +++ b/create-egg.sh @@ -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 . -.env-egg/bin/python setup.py bdist_egg \ No newline at end of file +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 diff --git a/examples/http-api/streamtorrent.py b/examples/http-api/streamtorrent.py index 72800ff..7ae9a7f 100644 --- a/examples/http-api/streamtorrent.py +++ b/examples/http-api/streamtorrent.py @@ -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, )) diff --git a/setup.py b/setup.py index 7324856..98ca2cf 100644 --- a/setup.py +++ b/setup.py @@ -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) ) diff --git a/streaming/__init__.py b/streaming/__init__.py index cccd34e..e047688 100644 --- a/streaming/__init__.py +++ b/streaming/__init__.py @@ -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) diff --git a/streaming/core.py b/streaming/core.py index b965941..8896052 100644 --- a/streaming/core.py +++ b/streaming/core.py @@ -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(): diff --git a/streaming/data/config.ui b/streaming/data/config.ui new file mode 100644 index 0000000..8f22af6 --- /dev/null +++ b/streaming/data/config.ui @@ -0,0 +1,712 @@ + + + + + + False + + + True + False + + + True + False + 0 + none + + + True + False + 10 + 12 + + + True + False + 5 + + + True + False + 5 + + + Download only streamed files, skip the other files + True + True + False + False + True + + + False + False + 0 + + + + + + + + + + + True + False + <b>Settings</b> + True + + + + + False + True + 0 + + + + + True + False + 0 + none + + + True + False + 10 + 12 + + + True + False + 5 + + + True + False + + + True + False + Hostname: + + + False + False + 0 + + + + + True + True + + True + False + False + True + True + + + True + True + 1 + + + + + True + True + 0 + + + + + True + False + + + True + False + Port: + + + False + False + 0 + + + + + True + True + + True + False + False + True + True + + + True + True + 1 + + + + + True + True + 1 + + + + + True + False + + + Enable Reverse Proxy + True + True + False + False + True + + + False + False + 0 + + + + + True + True + 2 + + + + + True + False + + + True + False + Reverse Proxy Base Url: + + + False + False + 0 + + + + + True + True + False + False + True + True + + + True + True + 1 + + + + + True + True + 3 + + + + + + True + False + 5 + + + + True + False + + + + True + False + 5 + + + Use SSL + True + True + False + False + True + + + True + True + 0 + + + + + True + False + 20 + + + True + False + + + Use Daemon/WebUI Certificate + True + True + False + False + True + True + + + True + True + 0 + + + + + Custom Certificate + True + True + False + False + True + True + input_ssl_cert_daemon + + + True + True + 1 + + + + + True + False + 20 + + + True + False + + + True + False + 0 + Private key file path + + + True + True + 0 + + + + + True + True + + False + False + True + True + + + True + True + 1 + + + + + True + False + 0 + Certificate and chains file path + + + True + True + 2 + + + + + True + True + + False + False + True + True + + + True + True + 3 + + + + + + + True + True + 2 + + + + + + + True + True + 1 + + + + + + + False + False + 1 + + + + + False + False + 5 + + + + + + + + + True + False + <b>File Serving Settings</b> + True + + + + + False + True + 1 + + + + + True + False + 0 + none + + + True + False + 10 + 12 + + + True + False + 5 + + + True + False + 5 + + + Allow remote control + True + True + False + False + True + + + False + False + 0 + + + + + True + False + 32 + + + True + False + 5 + + + + True + False + 5 + + + True + False + Remote control password: + + + False + False + 0 + + + + + True + True + True + + True + False + False + True + True + + + True + True + 1 + + + + + False + False + 1 + + + + + True + False + 5 + + + True + False + Remote control url: + + + False + False + 0 + + + + + True + True + + True + False + False + True + True + False + + + True + True + 1 + + + + + False + False + 2 + + + + + + + False + False + 1 + + + + + False + False + 0 + + + + + Use stream protocol urls + True + True + False + False + True + + + False + False + 1 + + + + + Auto-open stream protocol urls + True + True + False + False + True + + + False + False + 2 + + + + + Aggressive prioritizing + True + True + False + False + True + + + False + False + 3 + + + + + + + + + True + False + <b>Advanced Settings</b> + True + + + + + False + True + 2 + + + + + + diff --git a/streaming/data/streaming.js b/streaming/data/streaming.js index a487525..9295d24 100644 --- a/streaming/data/streaming.js +++ b/streaming/data/streaming.js @@ -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() { diff --git a/streaming/gtk3ui.py b/streaming/gtk3ui.py new file mode 100644 index 0000000..4cfa283 --- /dev/null +++ b/streaming/gtk3ui.py @@ -0,0 +1,234 @@ +# +# gtkui.py +# +# Copyright (C) 2009 John Doee +# +# Basic plugin template created by: +# Copyright (C) 2008 Martijn Voncken +# Copyright (C) 2007-2009 Andrew Resch +# Copyright (C) 2009 Damien Churchill +# +# 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() \ No newline at end of file diff --git a/streaming/gtkui.py b/streaming/gtkui.py index a06b3a5..6317814 100644 --- a/streaming/gtkui.py +++ b/streaming/gtkui.py @@ -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): diff --git a/streaming/resource.py b/streaming/resource.py index ed63f93..ff1d6a2 100644 --- a/streaming/resource.py +++ b/streaming/resource.py @@ -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. diff --git a/streaming/torrentfile.py b/streaming/torrentfile.py index ee3b103..9dc3106 100644 --- a/streaming/torrentfile.py +++ b/streaming/torrentfile.py @@ -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: diff --git a/streaming/webui.py b/streaming/webui.py index f3e6d98..afeaf4d 100644 --- a/streaming/webui.py +++ b/streaming/webui.py @@ -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