mirror of
https://github.com/JohnDoee/deluge-streaming/
synced 2026-07-01 07:31:17 -07:00
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,7 +24,7 @@ _trial_temp
|
|||||||
*.komodoproject
|
*.komodoproject
|
||||||
docs/_build*
|
docs/_build*
|
||||||
.env*
|
.env*
|
||||||
|
EGG-INFO
|
||||||
|
|
||||||
# for bundling
|
# for bundling
|
||||||
thomas
|
thomas
|
||||||
|
|||||||
14
create-egg.sh
Normal file → Executable file
14
create-egg.sh
Normal file → Executable 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
|
||||||
@@ -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, ))
|
||||||
|
|||||||
5
setup.py
5
setup.py
@@ -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)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
712
streaming/data/config.ui
Normal 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"><b>Settings</b></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">•</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">•</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">•</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">•</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"><b>File Serving Settings</b></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">•</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">•</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"><b>Advanced Settings</b></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>
|
||||||
@@ -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
234
streaming/gtk3ui.py
Normal 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()
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user