mirror of
https://github.com/JohnDoee/deluge-streaming/
synced 2026-07-01 07:31:17 -07:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6846f4fc52 | ||
|
|
b8183d01dd | ||
|
|
da05a6de1b | ||
|
|
d058d1d914 | ||
|
|
934048dd46 | ||
|
|
fae5c777fc | ||
|
|
490a083b6e | ||
|
|
c314e9381b | ||
|
|
fb739cedbe |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
pytz
|
||||
|
||||
19
README.md
19
README.md
@@ -1,7 +1,7 @@
|
||||
# Streaming Plugin
|
||||
https://github.com/JohnDoee/deluge-streaming
|
||||
|
||||
(c)2016 by Anders Jensen <johndoee@tidalstream.org>
|
||||
(c)2019 by Anders Jensen <johndoee@tidalstream.org>
|
||||
|
||||
## Description
|
||||
|
||||
@@ -44,12 +44,17 @@ The _allow remote_ option is to allow remote add and stream of torrents.
|
||||
|
||||
## Todo
|
||||
|
||||
* [x] Add RAR streaming support
|
||||
* [ ] Better feedback in interface about streams
|
||||
* [ ] Better feedback when using API
|
||||
* [x] Reverse proxy improvement (e.g. port different than bind port)
|
||||
* [ ] Fix problems when removing torrent from Deluge (sea of errors)
|
||||
|
||||
# Important Deluge 2 information
|
||||
|
||||
While developing the Deluge 2 version of this plugin I hit a few problems that might be visible for you too.
|
||||
|
||||
* When shutting down Deluge an exception / error happens every time, this bug is reported.
|
||||
* Sometimes the Web UI does not load plugins correctly, try restarting Deluge and refresh your browser if this happens.
|
||||
|
||||
# HTTP API Usage
|
||||
|
||||
## Prerequisite
|
||||
@@ -102,6 +107,14 @@ List of URL GET Arguments
|
||||
|
||||
# Version Info
|
||||
|
||||
## Version 0.11.0
|
||||
* Initial support for Deluge 2 / Python 3
|
||||
* Added support for aggressive piece prioritization when it should not be necessary.
|
||||
* Fixed bug related to paused torrent with no data downloaded.
|
||||
|
||||
## Version 0.10.5
|
||||
* Added support for serving files inline
|
||||
|
||||
## Version 0.10.4
|
||||
* Trying to set max priority less as it destroys performance
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
virtualenv .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
|
||||
9
create-egg2.sh
Executable file
9
create-egg2.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
virtualenv .env-egg2
|
||||
.env-egg2/bin/pip install -U thomas
|
||||
ln -s .env-egg2/lib/python*/site-packages/thomas .
|
||||
ln -s .env-egg2/lib/python*/site-packages/rarfile.py .
|
||||
ln -s .env-egg2/lib/python*/site-packages/six.py .
|
||||
ln -s .env-egg2/lib/python*/site-packages/rfc6266.py .
|
||||
ln -s .env-egg2/lib/python*/site-packages/lepl .
|
||||
ln -s .env-egg2/lib/python*/site-packages/pytz .
|
||||
.env-egg2/bin/python setup.py bdist_egg
|
||||
9
create-egg3.sh
Executable file
9
create-egg3.sh
Executable file
@@ -0,0 +1,9 @@
|
||||
python3 -m venv .env-egg3
|
||||
.env-egg3/bin/pip install -U thomas
|
||||
ln -s .env-egg3/lib/python*/site-packages/thomas .
|
||||
ln -s .env-egg3/lib/python*/site-packages/rarfile.py .
|
||||
ln -s .env-egg3/lib/python*/site-packages/six.py .
|
||||
ln -s .env-egg3/lib/python*/site-packages/rfc6266.py .
|
||||
ln -s .env-egg3/lib/python*/site-packages/lepl .
|
||||
ln -s .env-egg3/lib/python*/site-packages/pytz .
|
||||
.env-egg3/bin/python setup.py bdist_egg
|
||||
@@ -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, ))
|
||||
|
||||
7
setup.py
7
setup.py
@@ -42,7 +42,7 @@ from setuptools import setup, find_packages
|
||||
__plugin_name__ = "Streaming"
|
||||
__author__ = "Anders Jensen"
|
||||
__author_email__ = "johndoee@tidalstream.org"
|
||||
__version__ = "0.10.4"
|
||||
__version__ = "0.11.0"
|
||||
__url__ = "https://github.com/JohnDoee/deluge-streaming"
|
||||
__license__ = "GPLv3"
|
||||
__description__ = "Enables streaming of files while downloading them."
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,10 +180,11 @@ 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()
|
||||
# TODO: unfinished_piece can be None
|
||||
for unfinished_piece, status in enumerate(self.torrent.status.pieces[best_reader_piece:], best_reader_piece):
|
||||
if not status and unfinished_piece not in downloading_pieces:
|
||||
break
|
||||
@@ -187,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)
|
||||
|
||||
@@ -382,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)
|
||||
@@ -487,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
|
||||
@@ -611,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:
|
||||
@@ -679,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(), ))
|
||||
@@ -747,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()
|
||||
@@ -809,7 +825,7 @@ class Core(CorePluginBase):
|
||||
|
||||
@export
|
||||
@defer.inlineCallbacks
|
||||
def stream_torrent(self, infohash=None, url=None, filedump=None, filepath_or_index=None, includes_name=False, wait_for_end_pieces=False, label=None):
|
||||
def stream_torrent(self, infohash=None, url=None, filedump=None, filepath_or_index=None, includes_name=False, wait_for_end_pieces=False, label=None, as_inline=False):
|
||||
logger.debug('Trying to stream infohash:%s, url:%s, filepath_or_index:%s' % (infohash, url, filepath_or_index))
|
||||
torrent = get_torrent(infohash)
|
||||
|
||||
@@ -827,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():
|
||||
@@ -851,7 +867,7 @@ class Core(CorePluginBase):
|
||||
|
||||
try:
|
||||
stream_or_item = yield defer.maybeDeferred(self.torrent_handler.stream, infohash, fn, wait_for_end_pieces=wait_for_end_pieces)
|
||||
stream_url = self.thomas_http_output.serve_item(stream_or_item)
|
||||
stream_url = self.thomas_http_output.serve_item(stream_or_item, as_inline=as_inline)
|
||||
except:
|
||||
logger.exception('Failed to stream torrent')
|
||||
defer.returnValue({'status': 'error', 'message': 'failed to stream torrent'})
|
||||
|
||||
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',
|
||||
style: 'margin-left: 12px;'
|
||||
}));
|
||||
|
||||
om.bind('aggressive_prioritizing', fieldset.add({
|
||||
xtype: 'checkbox',
|
||||
name: 'aggressive_prioritizing',
|
||||
boxLabel: 'Aggressive prioritizing',
|
||||
style: 'margin-left: 12px;'
|
||||
}));
|
||||
},
|
||||
|
||||
onApply: function() {
|
||||
@@ -349,20 +356,23 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
|
||||
deluge.preferences.addPage(this.prefsPage);
|
||||
|
||||
console.log('Streaming plugin loaded');
|
||||
var doStream = function (tid, fileIndex) {
|
||||
deluge.client.streaming.stream_torrent(tid, null, null, fileIndex, true, {
|
||||
var doStream = function (tid, fileIndex, asInline) {
|
||||
deluge.client.streaming.stream_torrent(tid, null, null, fileIndex, true, false, null, asInline, {
|
||||
success: function (result) {
|
||||
console.log('Got result', result);
|
||||
if (result.status == 'success') {
|
||||
var url = result.url;
|
||||
if (result.use_stream_urls) {
|
||||
url = 'stream+' + url;
|
||||
if (result.auto_open_stream_urls) {
|
||||
window.location.assign(url);
|
||||
return;
|
||||
if (asInline) {
|
||||
window.open(result.url, '_blank');
|
||||
} else {
|
||||
var url = result.url;
|
||||
if (result.use_stream_urls) {
|
||||
url = 'stream+' + url;
|
||||
if (result.auto_open_stream_urls) {
|
||||
window.location.assign(url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Ext.Msg.alert('Stream ready', 'URL for stream: <a target="_blank" href="' + url + '">' + url + '</a>');
|
||||
}
|
||||
Ext.Msg.alert('Stream ready', 'URL for stream: <a target="_blank" href="' + url + '">' + url + '</a>');
|
||||
} else {
|
||||
Ext.Msg.alert('Stream failed', 'Error message: ' + result.message);
|
||||
}
|
||||
@@ -370,6 +380,28 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
|
||||
})
|
||||
}
|
||||
|
||||
var triggerStreamFile = function (asInline) {
|
||||
var files = deluge.details.items.items[2];
|
||||
var nodes = files.getSelectionModel().getSelectedNodes();
|
||||
if (nodes) {
|
||||
var fileIndex = nodes[0].attributes.fileIndex;
|
||||
var tid = files.torrentId;
|
||||
if (fileIndex >= 0) {
|
||||
doStream(tid, fileIndex, asInline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deluge.menus.filePriorities.addMenuItem({
|
||||
id: 'playthis',
|
||||
text: 'Play in browser',
|
||||
iconCls: 'icon-resume',
|
||||
handler: function (item, event) {
|
||||
deluge.menus.filePriorities.hide();
|
||||
triggerStreamFile(true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
deluge.menus.filePriorities.addMenuItem({
|
||||
id: 'streamthis',
|
||||
@@ -377,15 +409,26 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
|
||||
iconCls: 'icon-down',
|
||||
handler: function (item, event) {
|
||||
deluge.menus.filePriorities.hide();
|
||||
var files = deluge.details.items.items[2];
|
||||
var nodes = files.getSelectionModel().getSelectedNodes();
|
||||
if (nodes) {
|
||||
var fileIndex = nodes[0].attributes.fileIndex;
|
||||
var tid = files.torrentId;
|
||||
if (fileIndex >= 0) {
|
||||
doStream(tid, fileIndex);
|
||||
}
|
||||
}
|
||||
triggerStreamFile(false);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var triggerStreamTorrent = function (asInline) {
|
||||
var ids = deluge.torrents.getSelectedIds();
|
||||
if (ids) {
|
||||
doStream(ids[0], null, asInline);
|
||||
}
|
||||
}
|
||||
|
||||
deluge.menus.torrent.addMenuItem({
|
||||
id: 'playthistorrent',
|
||||
text: 'Play in browser',
|
||||
iconCls: 'icon-resume',
|
||||
handler: function (item, event) {
|
||||
deluge.menus.torrent.hide();
|
||||
triggerStreamTorrent(true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -396,10 +439,7 @@ StreamingPlugin = Ext.extend(Deluge.Plugin, {
|
||||
iconCls: 'icon-down',
|
||||
handler: function (item, event) {
|
||||
deluge.menus.torrent.hide();
|
||||
var ids = deluge.torrents.getSelectedIds();
|
||||
if (ids) {
|
||||
doStream(ids[0]);
|
||||
}
|
||||
triggerStreamTorrent(false);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
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 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.ResponseType.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:
|
||||
subprocess.Popen(['xdg-open', url])
|
||||
except OSError:
|
||||
print 'Unable to open URL %s' % (url, )
|
||||
print('Unable to open URL %s' % (url, ))
|
||||
|
||||
|
||||
class GtkUI(GtkPluginBase):
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user