summaryrefslogtreecommitdiffstats
path: root/module/remote
diff options
context:
space:
mode:
Diffstat (limited to 'module/remote')
-rw-r--r--module/remote/apitypes.py10
-rw-r--r--module/remote/apitypes_debug.py2
-rw-r--r--module/remote/json_converter.py22
-rw-r--r--module/remote/pyload.thrift18
-rw-r--r--module/remote/wsbackend/AbstractHandler.py54
-rw-r--r--module/remote/wsbackend/ApiHandler.py13
-rw-r--r--module/remote/wsbackend/AsyncHandler.py35
7 files changed, 107 insertions, 47 deletions
diff --git a/module/remote/apitypes.py b/module/remote/apitypes.py
index bc53f5f7c..aaec2b3ce 100644
--- a/module/remote/apitypes.py
+++ b/module/remote/apitypes.py
@@ -297,13 +297,13 @@ class ProgressInfo(BaseObject):
self.download = download
class ServerStatus(BaseObject):
- __slots__ = ['queuedDownloads', 'totalDownloads', 'speed', 'pause', 'download', 'reconnect']
+ __slots__ = ['speed', 'files', 'notifications', 'paused', 'download', 'reconnect']
- def __init__(self, queuedDownloads=None, totalDownloads=None, speed=None, pause=None, download=None, reconnect=None):
- self.queuedDownloads = queuedDownloads
- self.totalDownloads = totalDownloads
+ def __init__(self, speed=None, files=None, notifications=None, paused=None, download=None, reconnect=None):
self.speed = speed
- self.pause = pause
+ self.files = files
+ self.notifications = notifications
+ self.paused = paused
self.download = download
self.reconnect = reconnect
diff --git a/module/remote/apitypes_debug.py b/module/remote/apitypes_debug.py
index 974a68c29..6d30f1da6 100644
--- a/module/remote/apitypes_debug.py
+++ b/module/remote/apitypes_debug.py
@@ -37,7 +37,7 @@ classes = {
'PackageInfo' : [int, basestring, basestring, int, int, basestring, basestring, basestring, int, (list, basestring), int, bool, int, PackageStats, (list, int), (list, int)],
'PackageStats' : [int, int, int, int],
'ProgressInfo' : [basestring, basestring, basestring, int, int, int, (None, DownloadProgress)],
- 'ServerStatus' : [int, int, int, bool, bool, bool],
+ 'ServerStatus' : [int, PackageStats, int, bool, bool, bool],
'ServiceDoesNotExists' : [basestring, basestring],
'ServiceException' : [basestring],
'TreeCollection' : [PackageInfo, (dict, int, FileInfo), (dict, int, PackageInfo)],
diff --git a/module/remote/json_converter.py b/module/remote/json_converter.py
index 256674c34..50f0309bd 100644
--- a/module/remote/json_converter.py
+++ b/module/remote/json_converter.py
@@ -14,7 +14,7 @@ from apitypes import ExceptionObject
# compact json separator
separators = (',', ':')
-# json encoder that accepts TBase objects
+# json encoder that accepts api objects
class BaseEncoder(json.JSONEncoder):
def default(self, o):
@@ -26,17 +26,35 @@ class BaseEncoder(json.JSONEncoder):
return json.JSONEncoder.default(self, o)
+# more compact representation, only clients with information of the classes can handle it
+class BaseEncoderCompact(json.JSONEncoder):
+
+ def default(self, o):
+ if isinstance(o, BaseObject) or isinstance(o, ExceptionObject):
+ ret = {"@compact" : [o.__class__.__name__]}
+ ret["@compact"].extend(getattr(o, attr) for attr in o.__slots__)
+ return ret
+
+ return json.JSONEncoder.default(self, o)
def convert_obj(dct):
if '@class' in dct:
cls = getattr(apitypes, dct['@class'])
del dct['@class']
return cls(**dct)
+ elif '@compact' in dct:
+ cls = getattr(apitypes, dct['@compact'][0])
+ return cls(*dct['@compact'][1:])
return dct
def dumps(*args, **kwargs):
- kwargs['cls'] = BaseEncoder
+ if 'compact' in kwargs:
+ kwargs['cls'] = BaseEncoderCompact
+ del kwargs['compact']
+ else:
+ kwargs['cls'] = BaseEncoder
+
kwargs['separators'] = separators
return json.dumps(*args, **kwargs)
diff --git a/module/remote/pyload.thrift b/module/remote/pyload.thrift
index c66ec20d6..dc6b1c406 100644
--- a/module/remote/pyload.thrift
+++ b/module/remote/pyload.thrift
@@ -128,15 +128,6 @@ struct ProgressInfo {
7: optional DownloadProgress download
}
-struct ServerStatus {
- 1: i16 queuedDownloads,
- 2: i16 totalDownloads,
- 3: ByteCount speed,
- 4: bool pause,
- 5: bool download,
- 6: bool reconnect
-}
-
// download info for specific file
struct DownloadInfo {
1: string url,
@@ -203,6 +194,15 @@ struct LinkStatus {
6: string packagename,
}
+struct ServerStatus {
+ 1: ByteCount speed,
+ 2: PackageStats files,
+ 3: i16 notifications,
+ 4: bool paused,
+ 5: bool download,
+ 6: bool reconnect,
+}
+
struct InteractionTask {
1: InteractionID iid,
2: Input input,
diff --git a/module/remote/wsbackend/AbstractHandler.py b/module/remote/wsbackend/AbstractHandler.py
index f843fc278..45fbb134c 100644
--- a/module/remote/wsbackend/AbstractHandler.py
+++ b/module/remote/wsbackend/AbstractHandler.py
@@ -41,11 +41,38 @@ class AbstractHandler:
def do_extra_handshake(self, req):
self.log.debug("WS Connected: %s" % req)
+ req.api = None #when api is set client is logged in
+
+ # allow login via session when webinterface is active
+ if self.core.config['webinterface']['activated']:
+ cookie = req.headers_in.getheader('Cookie')
+ s = self.load_session(cookie)
+ if s:
+ uid = s.get('uid', None)
+ req.api = self.api.withUserContext(uid)
+ self.log.debug("WS authenticated with cookie: %d" % uid)
+
self.on_open(req)
def on_open(self, req):
pass
+ def load_session(self, cookies):
+ from Cookie import SimpleCookie
+ from beaker.session import Session
+ from module.web.webinterface import session
+
+ cookies = SimpleCookie(cookies)
+ sid = cookies.get(session.options['key'])
+ if not sid:
+ return None
+
+ s = Session({}, use_cookies=False, id=sid.value, **session.options)
+ if s.is_new:
+ return None
+
+ return s
+
def passive_closing_handshake(self, req):
self.log.debug("WS Closed: %s" % req)
self.on_close(req)
@@ -59,8 +86,6 @@ class AbstractHandler:
def handle_call(self, msg, req):
""" Parses the msg for an argument call. If func is null an response was already sent.
- :param msg:
- :param req:
:return: func, args, kwargs
"""
try:
@@ -70,11 +95,15 @@ class AbstractHandler:
self.send_result(req, self.ERROR, "No JSON request")
return None, None, None
- if type(o) != list and len(o) not in range(1,4):
+ if not isinstance(o, basestring) and type(o) != list and len(o) not in range(1, 4):
self.log.debug("Invalid Api call: %s" % o)
self.send_result(req, self.ERROR, "Invalid Api call")
return None, None, None
- if len(o) == 1: # arguments omitted
+
+ # called only with name, no args
+ if isinstance(o, basestring):
+ return o, [], {}
+ elif len(o) == 1: # arguments omitted
return o[0], [], {}
elif len(o) == 2:
func, args = o
@@ -85,5 +114,20 @@ class AbstractHandler:
else:
return tuple(o)
+ def do_login(self, req, args, kwargs):
+ user = self.api.checkAuth(*args, **kwargs)
+ if user:
+ req.api = self.api.withUserContext(user.uid)
+ return self.send_result(req, self.OK, True)
+ else:
+ return self.send_result(req, self.FORBIDDEN, "Forbidden")
+
+ def do_logout(self, req):
+ req.api = None
+ return self.send_result(req, self.OK, True)
+
def send_result(self, req, code, result):
- return send_message(req, dumps([code, result])) \ No newline at end of file
+ return send_message(req, dumps([code, result]))
+
+ def send(self, req, obj):
+ return send_message(req, dumps(obj)) \ No newline at end of file
diff --git a/module/remote/wsbackend/ApiHandler.py b/module/remote/wsbackend/ApiHandler.py
index eec546d47..e985e10be 100644
--- a/module/remote/wsbackend/ApiHandler.py
+++ b/module/remote/wsbackend/ApiHandler.py
@@ -55,18 +55,9 @@ class ApiHandler(AbstractHandler):
return # handle_call already sent the result
if func == 'login':
- user = self.api.checkAuth(*args, **kwargs)
- if user:
- req.api = self.api.withUserContext(user.uid)
- return self.send_result(req, self.OK, True)
-
- else:
- return self.send_result(req, self.OK, False)
-
+ return self.do_login(req, args, kwargs)
elif func == 'logout':
- req.api = None
- return self.send_result(req, self.OK, True)
-
+ return self.do_logout(req)
else:
if not req.api:
return self.send_result(req, self.FORBIDDEN, "Forbidden")
diff --git a/module/remote/wsbackend/AsyncHandler.py b/module/remote/wsbackend/AsyncHandler.py
index a8382a211..2f9b43ad2 100644
--- a/module/remote/wsbackend/AsyncHandler.py
+++ b/module/remote/wsbackend/AsyncHandler.py
@@ -16,7 +16,7 @@
# @author: RaNaN
###############################################################################
-from Queue import Queue
+from Queue import Queue, Empty
from threading import Lock
from mod_pywebsocket.msgutil import receive_message
@@ -34,13 +34,13 @@ class AsyncHandler(AbstractHandler):
Progress information are continuous and will be pushed in a fixed interval when available.
After connect you have to login and can set the interval by sending the json command ["setInterval", xy].
- To start receiving updates call "start", afterwards no more incoming messages will be accept!
+ To start receiving updates call "start", afterwards no more incoming messages will be accepted!
"""
PATH = "/async"
COMMAND = "start"
- PROGRESS_INTERVAL = 1
+ PROGRESS_INTERVAL = 2
STATUS_INTERVAL = 60
def __init__(self, api):
@@ -57,7 +57,10 @@ class AsyncHandler(AbstractHandler):
@lock
def on_close(self, req):
- self.clients.remove(req)
+ try:
+ self.clients.remove(req)
+ except ValueError: # ignore when not in list
+ pass
@lock
def add_event(self, event):
@@ -86,21 +89,15 @@ class AsyncHandler(AbstractHandler):
return # Result was already sent
if func == 'login':
- user = self.api.checkAuth(*args, **kwargs)
- if user:
- req.api = self.api.withUserContext(user.uid)
- return self.send_result(req, self.OK, True)
-
- else:
- return self.send_result(req, self.FORBIDDEN, "Forbidden")
+ return self.do_login(req, args, kwargs)
elif func == 'logout':
- req.api = None
- return self.send_result(req, self.OK, True)
+ return self.do_logout(req)
else:
if not req.api:
return self.send_result(req, self.FORBIDDEN, "Forbidden")
+
if func == "setInterval":
req.interval = args[0]
elif func == self.COMMAND:
@@ -109,4 +106,14 @@ class AsyncHandler(AbstractHandler):
def mode_running(self, req):
""" Listen for events, closes socket when returning True """
- self.send_result(req, "update", "test") \ No newline at end of file
+ try:
+ ev = req.queue.get(True, req.interval)
+ self.send(req, ev)
+
+ except Empty:
+ # TODO: server status is not enough
+ # modify core api to include progress? think of other needed information to show
+ # notifications
+
+ self.send(req, self.api.getServerStatus())
+ self.send(req, self.api.getProgressInfo()) \ No newline at end of file