added authentication to remote api and reset when streamed file is complete

This commit is contained in:
JohnDoee
2015-03-16 22:21:00 +01:00
parent b6df778ce4
commit 40301ba6e7
5 changed files with 174 additions and 18 deletions

View File

@@ -66,6 +66,9 @@ DEFAULT_PREFS = {
'ip': '127.0.0.1',
'port': 46123,
'allow_remote': False,
'reset_complete': True,
'remote_username': 'username',
'remote_password': 'password',
}
from .filelike import FilelikeObjectResource
@@ -109,9 +112,9 @@ class FileServeResource(resource.Resource):
class AddTorrentResource(Resource):
isLeaf = True
def __init__(self, client):
def __init__(self, client, *args, **kwargs):
self.client = client
Resource.__init__(self)
Resource.__init__(self, *args, **kwargs)
@defer.inlineCallbacks
def render_POST(self, request):
@@ -131,9 +134,9 @@ class AddTorrentResource(Resource):
class StreamResource(Resource):
isLeaf = True
def __init__(self, client):
def __init__(self, client, *args, **kwargs):
self.client = client
Resource.__init__(self)
Resource.__init__(self, *args, **kwargs)
@defer.inlineCallbacks
def render_GET(self, request):
@@ -151,7 +154,7 @@ class StreamResource(Resource):
class TorrentFile(object):
file_handler = None
def __init__(self, torrent_handler, file_path, torrent_file_path, size, chunk_size, offset):
def __init__(self, torrent_handler, file_path, torrent_file_path, size, chunk_size, offset, file_index):
self.torrent_handler = torrent_handler
self.file_path = file_path
self.torrent_file_path = torrent_file_path
@@ -159,6 +162,7 @@ class TorrentFile(object):
self.last_chunk = (offset + size) / chunk_size
self.chunk_size = chunk_size
self.offset = offset
self.file_index = file_index
self.size = size
self.last_requested_chunk = self.first_chunk
self.is_closed = False
@@ -247,7 +251,8 @@ class TorrentFile(object):
return self.file_handler.close()
def copy(self):
tf = TorrentFile(self.torrent_handler, self.file_path, self.torrent_file_path, self.size, self.chunk_size, self.offset)
tf = TorrentFile(self.torrent_handler, self.file_path, self.torrent_file_path,
self.size, self.chunk_size, self.offset, self.file_index)
return tf
class TorrentHandler(object):
@@ -256,7 +261,7 @@ class TorrentHandler(object):
self.torrent_handle = torrent.handle
self.torrent_id = torrent_id
self.core = core
self.priorities = [0] * len(torrent.get_status(['files'])['files']) # TODO: get current priorities and use those instead
self.priorities = [0] * len(torrent.get_status(['files'])['files'])
self.torrent_files = defaultdict(list)
self.priorities_increased = {}
# need to blackhole all pieces not downloaded yet
@@ -340,14 +345,30 @@ class TorrentHandler(object):
return bool(handled_heads)
def update_chunk_priorities(self):
def update_chunk_priorities(self): # TODO: check if torrent still exists
file_progress = self.torrent.get_status(['file_progress'])['file_progress']
incomplete_files = False
for torrent_file_path, tfs in self.torrent_files.items():
if not tfs:
logger.debug('No heads left for %r' % torrent_file_path)
del self.torrent_files[torrent_file_path]
continue
tf = tfs[0]
if file_progress[tf.file_index] == 1.0:
logger.info('%s is already complete, skipping' % torrent_file_path)
continue
if self.update_chunk_priority(tfs):
self.last_activity = datetime.now()
incomplete_files = True
if not incomplete_files and self.core.config['reset_complete'] and not all(self.priorities):
logger.info('We are not doing any file streamings, but not downloading all files, changing that.')
self.priorities = [1] * len(self.priorities)
self.update_priorities()
if datetime.now() - self.last_activity > HANDLERS_TIMEOUT:
logger.debug('Torrent handler idle, killing myself.')
@@ -385,8 +406,23 @@ class TorrentHandler(object):
if progress == 1: # file is complete, no need to fire up all the torrent jazz
defer.returnValue(static.File(fp))
self.priorities = [0] * len(self.priorities)
self.priorities[f['index']] = 3
current_tfs = []
for tfs in self.torrent_files.values():
if not tfs:
continue
tf = tfs[0]
self.priorities[tf.file_index] = 3
current_tfs.append((tf, self.torrent_handle.piece_priorities()[tf.first_chunk:tf.last_chunk+1]))
self.update_priorities()
for tf, chunk_status in current_tfs:
logger.info('Setting chunks to old status')
for i, chunk in enumerate(chunk_status, tf.first_chunk):
logger.debug('Setting status on chunk %s back to %s' % (i, chunk))
self.torrent_handle.piece_priority(i, chunk)
self.torrent.resume()
@@ -394,7 +430,7 @@ class TorrentHandler(object):
size_pieces = int(min(math.ceil((EXPECTED_SIZE * 1.0) / piece_length), f['pieces']))
expected_pieces = max(percent_pieces, size_pieces) # we need to download either 5% or 5MB of the file before allowing stream.
tf = TorrentFile(self, fp, f['path'], f['size'], status['piece_length'], f['offset'])
tf = TorrentFile(self, fp, f['path'], f['size'], status['piece_length'], f['offset'], f['index'])
if len(self.torrent_files[f['path']]) == 1:
self.blackhole_all_pieces(tf.first_chunk, tf.last_chunk)
@@ -422,11 +458,14 @@ class Core(CorePluginBase):
self.resource = Resource()
self.resource.putChild('file', self.fsr)
if self.config['allow_remote']:
self.resource.putChild('add_torrent', AddTorrentResource(self))
self.resource.putChild('stream', StreamResource(self))
self.resource.putChild('add_torrent', AddTorrentResource(username=self.config['remote_username'],
password=self.config['remote_password'],
client=self))
self.resource.putChild('stream', StreamResource(username=self.config['remote_username'],
password=self.config['remote_password'],
client=self))
self.site = server.Site(self.resource)
self.listening = reactor.listenTCP(self.config.config['port'], self.site, interface=self.config.config['ip'])
session = component.get("Core").session
settings = session.get_settings()
@@ -434,6 +473,11 @@ class Core(CorePluginBase):
session.set_settings(settings)
self.torrent_handlers = {}
try:
self.listening = reactor.listenTCP(self.config['port'], self.site, interface=self.config['ip'])
except:
self.listening = reactor.listenTCP(self.config['port'], self.site, interface='127.0.0.1')
@defer.inlineCallbacks
def disable(self):
@@ -500,5 +544,5 @@ class Core(CorePluginBase):
defer.returnValue({
'status': 'success',
'url': 'http://%s:%s/file/%s/%s' % (self.config.config['ip'], self.config.config['port'],
self.fsr.add_file(tf), os.path.basename(tf.file_path))
self.fsr.add_file(tf), urllib.quote_plus(os.path.basename(tf.file_path)))
})

View File

@@ -10,6 +10,7 @@
<widget class="GtkVBox" id="label_box">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkLabel" id="label_ip">
<property name="visible">True</property>
@@ -20,6 +21,7 @@
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label_port">
<property name="visible">True</property>
@@ -30,6 +32,18 @@
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label_reset_complete">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Reset "do not download" when streamed file is complete:</property>
</widget>
<packing>
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label_allow_remote">
<property name="visible">True</property>
@@ -37,9 +51,32 @@
<property name="label" translatable="yes">Allow remote control:</property>
</widget>
<packing>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label_remote_username">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Remote username:</property>
</widget>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<widget class="GtkLabel" id="label_remote_password">
<property name="visible">True</property>
<property name="xalign">0</property>
<property name="label" translatable="yes">Remote password:</property>
</widget>
<packing>
<property name="position">5</property>
</packing>
</child>
</widget>
<packing>
<property name="position">0</property>
@@ -49,6 +86,7 @@
<widget class="GtkVBox" id="input_box">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<widget class="GtkEntry" id="input_ip">
<property name="visible">True</property>
@@ -58,6 +96,7 @@
<property name="position">0</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="input_port">
<property name="visible">True</property>
@@ -67,8 +106,9 @@
<property name="position">1</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="input_allow_remote">
<widget class="GtkCheckButton" id="input_reset_complete">
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
@@ -76,6 +116,38 @@
<property name="position">2</property>
</packing>
</child>
<child>
<widget class="GtkCheckButton" id="input_allow_remote">
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="position">3</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="input_remote_username">
<property name="visible">True</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="position">4</property>
</packing>
</child>
<child>
<widget class="GtkEntry" id="input_remote_password">
<property name="visible">True</property>
<property name="visibility">False</property>
<property name="can_focus">True</property>
</widget>
<packing>
<property name="position">5</property>
</packing>
</child>
</widget>
<packing>
<property name="position">1</property>

View File

@@ -84,7 +84,11 @@ class GtkUI(GtkPluginBase):
"ip": self.glade.get_widget("input_ip").get_text(),
"port": int(self.glade.get_widget("input_port").get_text()),
"allow_remote": self.glade.get_widget("input_allow_remote").get_active(),
"reset_complete": self.glade.get_widget("input_reset_complete").get_active(),
"remote_username": self.glade.get_widget("input_remote_username").get_text(),
"remote_password": self.glade.get_widget("input_remote_password").get_text(),
}
client.streaming.set_config(config)
def on_show_prefs(self):
@@ -95,6 +99,9 @@ class GtkUI(GtkPluginBase):
self.glade.get_widget("input_ip").set_text(config["ip"])
self.glade.get_widget("input_port").set_text(str(config["port"]))
self.glade.get_widget("input_allow_remote").set_active(config["allow_remote"])
self.glade.get_widget("input_reset_complete").set_active(config["reset_complete"])
self.glade.get_widget("input_remote_username").set_text(config["remote_username"])
self.glade.get_widget("input_remote_password").set_text(config["remote_password"])
def on_menuitem_stream(self, data=None):
torrent_id = component.get("TorrentView").get_selected_torrents()[0]

View File

@@ -1,16 +1,40 @@
from twisted.web.resource import Resource as TwistedResource, _computeAllowedMethods
from twisted.web import server
from twisted.web import server, error
from twisted.internet import defer
class Resource(TwistedResource):
content_type = 'application/json'
def render(self, request):
def __init__(self, username=None, password=None, *args, **kwargs):
self.username = username
self.password = password
TwistedResource.__init__(self, *args, **kwargs)
def render(self, request): # Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
"""
Adds support for deferred render methods
"""
auth_header = request.getHeader('Authorization')
if self.username or self.password:
authenticated = False
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(':')
if len(userpass) == 2:
username, password = userpass
if self.username == username and self.password == password:
authenticated = True
if not authenticated:
print auth_header
print self.username, self.password
request.setResponseCode(401)
return 'Unauthorized'
m = getattr(self, 'render_' + request.method, None)
if not m:
# This needs to be here until the deprecated subclasses of the