summaryrefslogtreecommitdiffstats
path: root/module/remote
diff options
context:
space:
mode:
Diffstat (limited to 'module/remote')
-rw-r--r--module/remote/JSONClient.py52
-rw-r--r--module/remote/WSClient.py41
-rw-r--r--module/remote/WebSocketBackend.py6
-rw-r--r--module/remote/json_converter.py26
-rw-r--r--module/remote/wsbackend/AbstractHandler.py29
-rw-r--r--module/remote/wsbackend/ApiHandler.py22
-rw-r--r--module/remote/wsbackend/AsyncHandler.py112
-rw-r--r--module/remote/wsbackend/EventHandler.py41
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