diff options
Diffstat (limited to 'module/remote')
-rw-r--r-- | module/remote/JSONClient.py | 52 | ||||
-rw-r--r-- | module/remote/WSClient.py | 41 | ||||
-rw-r--r-- | module/remote/WebSocketBackend.py | 6 | ||||
-rw-r--r-- | module/remote/json_converter.py | 26 | ||||
-rw-r--r-- | module/remote/wsbackend/AbstractHandler.py | 29 | ||||
-rw-r--r-- | module/remote/wsbackend/ApiHandler.py | 22 | ||||
-rw-r--r-- | module/remote/wsbackend/AsyncHandler.py | 112 | ||||
-rw-r--r-- | module/remote/wsbackend/EventHandler.py | 41 |
8 files changed, 252 insertions, 77 deletions
diff --git a/module/remote/JSONClient.py b/module/remote/JSONClient.py new file mode 100644 index 000000000..52b712c81 --- /dev/null +++ b/module/remote/JSONClient.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from json_converter import loads, dumps +from urllib import urlopen, urlencode +from httplib import UNAUTHORIZED, FORBIDDEN + +class JSONClient: + URL = "http://localhost:8001/api" + + def __init__(self, url=None): + self.url = url or self.URL + self.session = None + + def request(self, path, data): + ret = urlopen(self.url + path, urlencode(data)) + if ret.code == 404: + raise AttributeError("Unknown Method") + if ret.code == 500: + raise Exception("Remote Exception") + if ret.code == UNAUTHORIZED: + raise Exception("Unauthorized") + if ret.code == FORBIDDEN: + raise Exception("Forbidden") + return ret.read() + + def login(self, username, password): + self.session = loads(self.request("/login", {'username': username, 'password': password})) + return self.session + + def logout(self): + self.call("logout") + self.session = None + + def call(self, func, *args, **kwargs): + # Add the current session + kwargs["session"] = self.session + path = "/" + func + "/" + "/".join(dumps(x) for x in args) + data = dict((k, dumps(v)) for k, v in kwargs.iteritems()) + rep = self.request(path, data) + return loads(rep) + + def __getattr__(self, item): + def call(*args, **kwargs): + return self.call(item, *args, **kwargs) + + return call + +if __name__ == "__main__": + api = JSONClient() + api.login("User", "test") + print api.getServerVersion()
\ No newline at end of file diff --git a/module/remote/WSClient.py b/module/remote/WSClient.py new file mode 100644 index 000000000..c06bab661 --- /dev/null +++ b/module/remote/WSClient.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from json_converter import loads, dumps +from websocket import create_connection + +class WSClient: + URL = "ws://localhost:7227/api" + + def __init__(self, url=None): + self.url = url or self.URL + self.ws = None + + def login(self, username, password): + self.ws = create_connection(self.URL) + return self.call("login", username, password) + + def logout(self): + self.call("logout") + self.ws.close() + + def call(self, func, *args, **kwargs): + self.ws.send(dumps([func, args, kwargs])) + code, result = loads(self.ws.recv()) + if code == 404: + raise AttributeError("Unknown Method") + elif code == 505: + raise Exception("Remote Exception") + + return result + + def __getattr__(self, item): + def call(*args, **kwargs): + return self.call(item, *args, **kwargs) + + return call + +if __name__ == "__main__": + api = WSClient() + api.login("User", "test") + print api.getServerVersion()
\ No newline at end of file diff --git a/module/remote/WebSocketBackend.py b/module/remote/WebSocketBackend.py index 6e01dad87..2d22664c6 100644 --- a/module/remote/WebSocketBackend.py +++ b/module/remote/WebSocketBackend.py @@ -33,14 +33,14 @@ class WebSocketBackend(BackendBase): from wsbackend.Server import WebSocketServer, DefaultOptions from wsbackend.Dispatcher import Dispatcher from wsbackend.ApiHandler import ApiHandler - from wsbackend.EventHandler import EventHandler + from wsbackend.AsyncHandler import AsyncHandler options = DefaultOptions() options.server_host = host options.port = port options.dispatcher = Dispatcher() - options.dispatcher.addHandler('/api', ApiHandler(self.core.api)) - options.dispatcher.addHandler('/events', EventHandler(self.core.api)) + options.dispatcher.addHandler(ApiHandler.PATH, ApiHandler(self.core.api)) + options.dispatcher.addHandler(AsyncHandler.PATH, AsyncHandler(self.core.api)) self.server = WebSocketServer(options) diff --git a/module/remote/json_converter.py b/module/remote/json_converter.py index 57e85fd16..ea76842a6 100644 --- a/module/remote/json_converter.py +++ b/module/remote/json_converter.py @@ -1,7 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from module.common.json_layer import json +try: + from module.common.json_layer import json +except ImportError: + import json + + +import ttypes from ttypes import BaseObject # json encoder that accepts TBase objects @@ -16,18 +22,12 @@ class BaseEncoder(json.JSONEncoder): return json.JSONEncoder.default(self, o) -class BaseDecoder(json.JSONDecoder): - - def __init__(self, *args, **kwargs): - json.JSONDecoder.__init__(self, *args, **kwargs) - self.object_hook = self.convertObject - - def convertObject(self, dct): - if '@class' in dct: - # TODO: convert - pass - return dct +def convert_obj(dct): + if '@class' in dct: + cls = getattr(ttypes, dct['@class']) + del dct['@class'] + return cls(**dct) def dumps(*args, **kwargs): kwargs['cls'] = BaseEncoder @@ -35,5 +35,5 @@ def dumps(*args, **kwargs): def loads(*args, **kwargs): - kwargs['cls'] = BaseDecoder + kwargs['object_hook'] = convert_obj return json.loads(*args, **kwargs)
\ No newline at end of file diff --git a/module/remote/wsbackend/AbstractHandler.py b/module/remote/wsbackend/AbstractHandler.py index 291dbf100..276f6fa38 100644 --- a/module/remote/wsbackend/AbstractHandler.py +++ b/module/remote/wsbackend/AbstractHandler.py @@ -25,10 +25,18 @@ class AbstractHandler: """ Abstract Handler providing common methods shared across WebSocket handlers """ + PATH = "/" + + OK = 200 + UNAUTHORIZED = 401 + FORBIDDEN = 403 + NOT_FOUND = 404 + ERROR = 500 def __init__(self, api): self.log = get_class_logger() self.api = api + self.core = api.core def do_extra_handshake(self, req): self.log.debug("WS Connected: %s" % req) @@ -58,22 +66,23 @@ class AbstractHandler: o = loads(msg) except ValueError, e: #invalid json object self.log.debug("Invalid Request: %s" % e) + self.send_result(req, self.ERROR, "No JSON request") return None, None, None - if type(o) != list and len(o) > 2: + if type(o) != list and len(o) not in range(1,4): self.log.debug("Invalid Api call: %s" % o) - self.send_result(req, 500, "Invalid Api call") + self.send_result(req, self.ERROR, "Invalid Api call") return None, None, None if len(o) == 1: # arguments omitted - o.append([]) - - func, args = o - if type(args) == list: - kwargs = {} + return o[0], [], {} + elif len(o) == 2: + func, args = o + if type(args) == list: + return func, args, {} + else: + return func, [], args else: - args, kwargs = [], args - - return func, args, kwargs + return tuple(o) def send_result(self, req, code, result): return send_message(req, dumps([code, result]))
\ No newline at end of file diff --git a/module/remote/wsbackend/ApiHandler.py b/module/remote/wsbackend/ApiHandler.py index 57d9ecd5b..e8ba80982 100644 --- a/module/remote/wsbackend/ApiHandler.py +++ b/module/remote/wsbackend/ApiHandler.py @@ -34,14 +34,15 @@ class ApiHandler(AbstractHandler): Non json request will be ignored. """ + PATH = "/api" + def transfer_data(self, req): while True: try: line = receive_message(req) except TypeError, e: # connection closed self.log.debug("WS Error: %s" % e) - self.passive_closing_handshake(req) - return + return self.passive_closing_handshake(req) self.handle_message(line, req) @@ -55,30 +56,31 @@ class ApiHandler(AbstractHandler): user = self.api.checkAuth(*args, **kwargs) if user: req.api = self.api.withUserContext(user.uid) - return self.send_result(req, 200, True) + return self.send_result(req, self.OK, True) else: - return self.send_result(req, 403, "Forbidden") + return self.send_result(req, self.FORBIDDEN, "Forbidden") elif func == 'logout': req.api = None - return self.send_result(req, 200, True) + return self.send_result(req, self.OK, True) else: if not req.api: - return self.send_result(req, 403, "Forbidden") + return self.send_result(req, self.FORBIDDEN, "Forbidden") if not self.api.isAuthorized(func, req.api.user): - return self.send_result(req, 401, "Unauthorized") + return self.send_result(req, self.UNAUTHORIZED, "Unauthorized") try: result = getattr(req.api, func)(*args, **kwargs) except AttributeError: - return self.send_result(req, 404, "Not Found") + return self.send_result(req, self.NOT_FOUND, "Not Found") except Exception, e: - return self.send_result(req, 500, str(e)) + self.core.print_exc() + return self.send_result(req, self.ERROR, str(e)) # None is invalid json type if result is None: result = True - return self.send_result(req, 200, result)
\ No newline at end of file + return self.send_result(req, self.OK, result)
\ No newline at end of file diff --git a/module/remote/wsbackend/AsyncHandler.py b/module/remote/wsbackend/AsyncHandler.py new file mode 100644 index 000000000..a8382a211 --- /dev/null +++ b/module/remote/wsbackend/AsyncHandler.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +############################################################################### +# Copyright(c) 2008-2012 pyLoad Team +# http://www.pyload.org +# +# This file is part of pyLoad. +# pyLoad is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# Subjected to the terms and conditions in LICENSE +# +# @author: RaNaN +############################################################################### + +from Queue import Queue +from threading import Lock + +from mod_pywebsocket.msgutil import receive_message + +from module.utils import lock +from AbstractHandler import AbstractHandler + +class Mode: + STANDBY = 1 + RUNNING = 2 + +class AsyncHandler(AbstractHandler): + """ + Handler that provides asynchronous information about server status, running downloads, occurred events. + + 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! + """ + + PATH = "/async" + COMMAND = "start" + + PROGRESS_INTERVAL = 1 + STATUS_INTERVAL = 60 + + def __init__(self, api): + AbstractHandler.__init__(self, api) + self.clients = [] + self.lock = Lock() + + @lock + def on_open(self, req): + req.queue = Queue() + req.interval = self.PROGRESS_INTERVAL + req.mode = Mode.STANDBY + self.clients.append(req) + + @lock + def on_close(self, req): + self.clients.remove(req) + + @lock + def add_event(self, event): + for req in self.clients: + req.queue.put(event) + + def transfer_data(self, req): + while True: + + if req.mode == Mode.STANDBY: + try: + line = receive_message(req) + except TypeError, e: # connection closed + self.log.debug("WS Error: %s" % e) + return self.passive_closing_handshake(req) + + self.mode_standby(line, req) + else: + if self.mode_running(req): + return self.passive_closing_handshake(req) + + def mode_standby(self, msg, req): + """ accepts calls before pushing updates """ + func, args, kwargs = self.handle_call(msg, req) + if not func: + 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") + + elif func == 'logout': + req.api = None + return self.send_result(req, self.OK, True) + + else: + if not req.api: + return self.send_result(req, self.FORBIDDEN, "Forbidden") + if func == "setInterval": + req.interval = args[0] + elif func == self.COMMAND: + req.mode = Mode.RUNNING + + + 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 diff --git a/module/remote/wsbackend/EventHandler.py b/module/remote/wsbackend/EventHandler.py deleted file mode 100644 index 2550ff2eb..000000000 --- a/module/remote/wsbackend/EventHandler.py +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -############################################################################### -# Copyright(c) 2008-2012 pyLoad Team -# http://www.pyload.org -# -# This file is part of pyLoad. -# pyLoad is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# Subjected to the terms and conditions in LICENSE -# -# @author: RaNaN -############################################################################### - -from threading import Lock - - -from module.utils import lock -from AbstractHandler import AbstractHandler - -class EventHandler(AbstractHandler): - - def __init__(self, api): - AbstractHandler.__init__(self, api) - self.clients = [] - self.lock = Lock() - - @lock - def on_open(self, req): - self.clients.append(req) - - @lock - def on_close(self, req): - self.clients.remove(req) - - def handle_message(self, line, req): - pass
\ No newline at end of file |