diff options
Diffstat (limited to 'module/remote')
-rw-r--r-- | module/remote/apitypes.py | 10 | ||||
-rw-r--r-- | module/remote/apitypes_debug.py | 2 | ||||
-rw-r--r-- | module/remote/json_converter.py | 22 | ||||
-rw-r--r-- | module/remote/pyload.thrift | 18 | ||||
-rw-r--r-- | module/remote/wsbackend/AbstractHandler.py | 54 | ||||
-rw-r--r-- | module/remote/wsbackend/ApiHandler.py | 13 | ||||
-rw-r--r-- | module/remote/wsbackend/AsyncHandler.py | 35 |
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 |