move the json resource into its own module
split the json-rpc methods and the rpc server into seperate classes make the json-rpc server a component to allow for extension
This commit is contained in:
parent
344446a626
commit
0ac64da5c6
|
@ -0,0 +1,30 @@
|
|||
#
|
||||
# deluge/ui/web/auth.py
|
||||
#
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
AUTH_LEVEL_NONE = 0
|
||||
AUTH_LEVEL_READONLY = 1
|
||||
AUTH_LEVEL_NORMAL = 5
|
||||
AUTH_LEVEL_ADMIN = 10
|
||||
|
||||
AUTH_LEVEL_DEFAULT = AUTH_LEVEL_NORMAL
|
|
@ -0,0 +1,40 @@
|
|||
#
|
||||
# deluge/ui/web/common.py
|
||||
#
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import gettext
|
||||
from mako.template import Template as MakoTemplate
|
||||
from deluge import common
|
||||
|
||||
_ = gettext.gettext
|
||||
|
||||
class Template(MakoTemplate):
|
||||
|
||||
builtins = {
|
||||
"_": _,
|
||||
"version": common.get_version()
|
||||
}
|
||||
|
||||
def render(self, *args, **data):
|
||||
data.update(self.builtins)
|
||||
return MakoTemplate.render(self, *args, **data)
|
|
@ -0,0 +1,413 @@
|
|||
#
|
||||
# deluge/ui/web/json_api.py
|
||||
#
|
||||
# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
|
||||
#
|
||||
# Deluge is free software.
|
||||
#
|
||||
# You may redistribute it and/or modify it under the terms of the
|
||||
# GNU General Public License, as published by the Free Software
|
||||
# Foundation; either version 3 of the License, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# deluge is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with deluge. If not, write to:
|
||||
# The Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.web import http, resource, server
|
||||
|
||||
from deluge import common, component
|
||||
from deluge.configmanager import ConfigManager
|
||||
from deluge.ui.client import client, Client
|
||||
from deluge.ui.web.auth import *
|
||||
from deluge.ui.web.common import _
|
||||
json = common.json
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
def export(auth_level=AUTH_LEVEL_DEFAULT):
|
||||
"""
|
||||
Decorator function to register an object's method as an RPC. The object
|
||||
will need to be registered with an `:class:RPCServer` to be effective.
|
||||
|
||||
:param func: function, the function to export
|
||||
:param auth_level: int, the auth level required to call this method
|
||||
|
||||
"""
|
||||
def wrap(func, *args, **kwargs):
|
||||
func._json_export = True
|
||||
func._json_auth_level = auth_level
|
||||
return func
|
||||
|
||||
return wrap
|
||||
|
||||
class JSONException(Exception):
|
||||
def __init__(self, inner_exception):
|
||||
self.inner_exception = inner_exception
|
||||
Exception.__init__(self, str(inner_exception))
|
||||
|
||||
class JSON(resource.Resource, component.Component):
|
||||
"""
|
||||
A Twisted Web resource that exposes a JSON-RPC interface for web clients
|
||||
to use.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
resource.Resource.__init__(self)
|
||||
component.Component.__init__(self, "JSON")
|
||||
self._remote_methods = []
|
||||
self._local_methods = {}
|
||||
|
||||
def connect(self, host="localhost", port=58846, username="", password=""):
|
||||
"""
|
||||
Connects the client to a daemon
|
||||
"""
|
||||
d = Deferred()
|
||||
_d = client.connect(host, port, username, password)
|
||||
|
||||
def on_get_methods(methods):
|
||||
"""
|
||||
Handles receiving the method names
|
||||
"""
|
||||
self._remote_methods = methods
|
||||
methods = list(self._remote_methods)
|
||||
methods.extend(self._local_methods)
|
||||
d.callback(methods)
|
||||
|
||||
def on_client_connected(connection_id):
|
||||
"""
|
||||
Handles the client successfully connecting to the daemon and
|
||||
invokes retrieving the method names.
|
||||
"""
|
||||
d = client.daemon.get_method_list()
|
||||
d.addCallback(on_get_methods)
|
||||
_d.addCallback(on_client_connected)
|
||||
return d
|
||||
|
||||
def _exec_local(self, method, params):
|
||||
"""
|
||||
Handles executing all local methods.
|
||||
"""
|
||||
if method == "system.listMethods":
|
||||
d = Deferred()
|
||||
methods = list(self._remote_methods)
|
||||
methods.extend(self._local_methods)
|
||||
d.callback(methods)
|
||||
return d
|
||||
elif method in self._local_methods:
|
||||
# This will eventually process methods that the server adds
|
||||
# and any plugins.
|
||||
return self._local_methods[method](*params)
|
||||
raise JSONException("Unknown system method")
|
||||
|
||||
def _exec_remote(self, method, params):
|
||||
"""
|
||||
Executes methods using the Deluge client.
|
||||
"""
|
||||
component, method = method.split(".")
|
||||
return getattr(getattr(client, component), method)(*params)
|
||||
|
||||
def _handle_request(self, request):
|
||||
"""
|
||||
Takes some json data as a string and attempts to decode it, and process
|
||||
the rpc object that should be contained, returning a deferred for all
|
||||
procedure calls and the request id.
|
||||
"""
|
||||
request_id = None
|
||||
try:
|
||||
request = json.loads(request)
|
||||
except ValueError:
|
||||
raise JSONException("JSON not decodable")
|
||||
|
||||
if "method" not in request or "id" not in request or \
|
||||
"params" not in request:
|
||||
raise JSONException("Invalid JSON request")
|
||||
|
||||
method, params = request["method"], request["params"]
|
||||
request_id = request["id"]
|
||||
|
||||
try:
|
||||
if method.startswith("system."):
|
||||
return self._exec_local(method, params), request_id
|
||||
elif method in self._local_methods:
|
||||
return self._exec_local(method, params), request_id
|
||||
elif method in self._remote_methods:
|
||||
return self._exec_remote(method, params), request_id
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
d = Deferred()
|
||||
d.callback(None)
|
||||
return d, request_id
|
||||
|
||||
def _on_rpc_request_finished(self, result, response, request):
|
||||
"""
|
||||
Sends the response of any rpc calls back to the json-rpc client.
|
||||
"""
|
||||
response["result"] = result
|
||||
return self._send_response(request, response)
|
||||
|
||||
def _on_rpc_request_failed(self, reason, response, request):
|
||||
"""
|
||||
Handles any failures that occured while making an rpc call.
|
||||
"""
|
||||
print type(reason)
|
||||
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
return ""
|
||||
|
||||
def _on_json_request(self, request):
|
||||
"""
|
||||
Handler to take the json data as a string and pass it on to the
|
||||
_handle_request method for further processing.
|
||||
"""
|
||||
log.debug("json-request: %s", request.json)
|
||||
response = {"result": None, "error": None, "id": None}
|
||||
d, response["id"] = self._handle_request(request.json)
|
||||
d.addCallback(self._on_rpc_request_finished, response, request)
|
||||
d.addErrback(self._on_rpc_request_failed, response, request)
|
||||
return d
|
||||
|
||||
def _on_json_request_failed(self, reason, request):
|
||||
"""
|
||||
Errback handler to return a HTTP code of 500.
|
||||
"""
|
||||
log.exception(reason)
|
||||
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
return ""
|
||||
|
||||
def _send_response(self, request, response):
|
||||
response = json.dumps(response)
|
||||
request.setHeader("content-type", "application/x-json")
|
||||
request.write(response)
|
||||
request.finish()
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Handles all the POST requests made to the /json controller.
|
||||
"""
|
||||
|
||||
if request.method != "POST":
|
||||
request.setResponseCode(http.NOT_ALLOWED)
|
||||
return ""
|
||||
|
||||
try:
|
||||
request.content.seek(0)
|
||||
request.json = request.content.read()
|
||||
d = self._on_json_request(request)
|
||||
return server.NOT_DONE_YET
|
||||
except Exception, e:
|
||||
return self._on_json_request_failed(e, request)
|
||||
|
||||
def register_object(self, obj, name=None):
|
||||
"""
|
||||
Registers an object to export it's rpc methods. These methods should
|
||||
be exported with the export decorator prior to registering the object.
|
||||
|
||||
:param obj: object, the object that we want to export
|
||||
:param name: str, the name to use, if None, it will be the class name of the object
|
||||
"""
|
||||
name = name or obj.__class__.__name__
|
||||
name = name.lower()
|
||||
|
||||
for d in dir(obj):
|
||||
if d[0] == "_":
|
||||
continue
|
||||
if getattr(getattr(obj, d), '_json_export', False):
|
||||
log.debug("Registering method: %s", name + "." + d)
|
||||
self._local_methods[name + "." + d] = getattr(obj, d)
|
||||
|
||||
class JSONComponent(component.Component):
|
||||
def __init__(self, name, interval=1, depend=None):
|
||||
super(JSONComponent, self).__init__(name, interval, depend)
|
||||
self._json = component.get("JSON")
|
||||
self._json.register_object(self, name)
|
||||
|
||||
|
||||
DEFAULT_HOST = "127.0.0.1"
|
||||
DEFAULT_PORT = 58846
|
||||
|
||||
DEFAULT_HOSTS = {
|
||||
"hosts": [(hashlib.sha1(str(time.time())).hexdigest(),
|
||||
DEFAULT_HOST, DEFAULT_PORT, "", "")]
|
||||
}
|
||||
|
||||
class WebApi(JSONComponent):
|
||||
def __init__(self):
|
||||
super(WebApi, self).__init__("Web")
|
||||
self.host_list = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
|
||||
|
||||
@export()
|
||||
def connect(self, host_id):
|
||||
d = Deferred()
|
||||
def on_connected(methods):
|
||||
d.callback(methods)
|
||||
for host in self.host_list["hosts"]:
|
||||
if host_id != host[0]:
|
||||
continue
|
||||
self._json.connect(*host[1:]).addCallback(on_connected)
|
||||
return d
|
||||
|
||||
@export()
|
||||
def connected(self):
|
||||
d = Deferred()
|
||||
d.callback(client.connected())
|
||||
return d
|
||||
|
||||
@export()
|
||||
def update_ui(self, keys, filter_dict):
|
||||
|
||||
ui_info = {
|
||||
"torrents": None,
|
||||
"filters": None,
|
||||
"stats": None
|
||||
}
|
||||
|
||||
d = Deferred()
|
||||
|
||||
def got_stats(stats):
|
||||
ui_info["stats"] = stats
|
||||
d.callback(ui_info)
|
||||
|
||||
def got_filters(filters):
|
||||
ui_info["filters"] = filters
|
||||
client.core.get_stats().addCallback(got_stats)
|
||||
|
||||
def got_torrents(torrents):
|
||||
ui_info["torrents"] = torrents
|
||||
client.core.get_filter_tree().addCallback(got_filters)
|
||||
client.core.get_torrents_status(filter_dict, keys).addCallback(got_torrents)
|
||||
return d
|
||||
|
||||
@export()
|
||||
def download_torrent_from_url(self, url):
|
||||
"""
|
||||
input:
|
||||
url: the url of the torrent to download
|
||||
|
||||
returns:
|
||||
filename: the temporary file name of the torrent file
|
||||
"""
|
||||
tmp_file = os.path.join(tempfile.gettempdir(), url.split("/")[-1])
|
||||
filename, headers = urllib.urlretrieve(url, tmp_file)
|
||||
log.debug("filename: %s", filename)
|
||||
d = Deferred()
|
||||
d.callback(filename)
|
||||
return d
|
||||
|
||||
@export()
|
||||
def get_torrent_info(self, filename):
|
||||
"""
|
||||
Goal:
|
||||
allow the webui to retrieve data about the torrent
|
||||
|
||||
input:
|
||||
filename: the filename of the torrent to gather info about
|
||||
|
||||
returns:
|
||||
{
|
||||
"filename": the torrent file
|
||||
"name": the torrent name
|
||||
"size": the total size of the torrent
|
||||
"files": the files the torrent contains
|
||||
"info_hash" the torrents info_hash
|
||||
}
|
||||
"""
|
||||
d = Deferred()
|
||||
d.callback(uicommon.get_torrent_info(filename.strip()))
|
||||
return d
|
||||
|
||||
@export()
|
||||
def add_torrents(self, torrents):
|
||||
"""
|
||||
input:
|
||||
torrents [{
|
||||
path: the path of the torrent file,
|
||||
options: the torrent options
|
||||
}]
|
||||
"""
|
||||
for torrent in torrents:
|
||||
filename = os.path.basename(torrent["path"])
|
||||
fdump = open(torrent["path"], "r").read()
|
||||
client.add_torrent_file(filename, fdump, torrent["options"])
|
||||
d = Deferred()
|
||||
d.callback(True)
|
||||
return d
|
||||
|
||||
@export()
|
||||
def login(self, password):
|
||||
"""Method to allow the webui to authenticate
|
||||
"""
|
||||
config = component.get("DelugeWeb").config
|
||||
m = hashlib.md5()
|
||||
m.update(config['pwd_salt'])
|
||||
m.update(password)
|
||||
d = Deferred()
|
||||
d.callback(m.hexdigest() == config['pwd_md5'])
|
||||
return d
|
||||
|
||||
@export()
|
||||
def get_hosts(self):
|
||||
"""Return the hosts in the hostlist"""
|
||||
hosts = dict((host[0], host[:]) for host in self.host_list["hosts"])
|
||||
|
||||
main_deferred = Deferred()
|
||||
def run_check():
|
||||
if all(map(lambda x: x[3] is not None, hosts.values())):
|
||||
main_deferred.callback(hosts.values())
|
||||
|
||||
def on_connect(result, c, host_id):
|
||||
def on_info(info, c):
|
||||
hosts[host_id][3] = _("Online")
|
||||
hosts[host_id][4] = info
|
||||
c.disconnect()
|
||||
run_check()
|
||||
|
||||
def on_info_fail(reason):
|
||||
hosts[host_id][3] = _("Offline")
|
||||
run_check()
|
||||
|
||||
if not c.connected():
|
||||
hosts[host_id][3] = _("Offline")
|
||||
run_check()
|
||||
return
|
||||
|
||||
d = c.daemon.info()
|
||||
d.addCallback(on_info, c)
|
||||
d.addErrback(on_info_fail)
|
||||
|
||||
def on_connect_failed(reason, host_id):
|
||||
log.exception(reason)
|
||||
hosts[host_id][3] = _("Offline")
|
||||
run_check()
|
||||
|
||||
for host in hosts.values():
|
||||
host_id, host, port, user, password = host[0:5]
|
||||
hosts[host_id][3:4] = (None, None)
|
||||
|
||||
if client.connected() and \
|
||||
(host, port, user) == client.connection_info():
|
||||
def on_info(info):
|
||||
hosts[host_id][4] = info
|
||||
run_check()
|
||||
host[5] = _("Connected")
|
||||
client.daemon.info().addCallback(on_info)
|
||||
hosts[host_id] = host
|
||||
continue
|
||||
|
||||
c = Client()
|
||||
d = c.connect(host, port, user, password)
|
||||
d.addCallback(on_connect, c, host_id)
|
||||
d.addErrback(on_connect_failed, host_id)
|
||||
return main_deferred
|
|
@ -36,19 +36,16 @@ import pkg_resources
|
|||
|
||||
from twisted.application import service, internet
|
||||
from twisted.internet import reactor
|
||||
from twisted.internet.defer import Deferred
|
||||
from twisted.web import http, resource, server, static
|
||||
|
||||
from mako.template import Template as MakoTemplate
|
||||
|
||||
from deluge import common, component
|
||||
from deluge.configmanager import ConfigManager
|
||||
from deluge.log import setupLogger, LOG as _log
|
||||
from deluge.ui import common as uicommon
|
||||
from deluge.ui.client import client, Client
|
||||
from deluge.ui.tracker_icons import TrackerIcons
|
||||
from deluge.ui.web.common import Template
|
||||
from deluge.ui.web.json_api import JSON, WebApi
|
||||
log = logging.getLogger(__name__)
|
||||
json = common.json
|
||||
|
||||
# Initialize gettext
|
||||
try:
|
||||
|
@ -70,8 +67,8 @@ current_dir = os.path.dirname(__file__)
|
|||
CONFIG_DEFAULTS = {
|
||||
"port": 8112,
|
||||
"template": "slate",
|
||||
"pwd_salt": u"2\xe8\xc7\xa6(n\x81_\x8f\xfc\xdf\x8b\xd1\x1e\xd5\x90",
|
||||
"pwd_md5": u".\xe8w\\+\xec\xdb\xf2id4F\xdb\rUc",
|
||||
"pwd_salt": "16f65d5c79b7e93278a28b60fed2431e",
|
||||
"pwd_md5": "2c9baa929ca38fb5c9eb5b054474d1ce",
|
||||
"base": "",
|
||||
"sessions": [],
|
||||
"sidebar_show_zero": False,
|
||||
|
@ -81,354 +78,12 @@ CONFIG_DEFAULTS = {
|
|||
"https": False
|
||||
}
|
||||
|
||||
DEFAULT_HOST = "127.0.0.1"
|
||||
DEFAULT_PORT = 58846
|
||||
DEFAULT_HOSTS = {
|
||||
"hosts": [[hashlib.sha1(str(time.time())).hexdigest(), DEFAULT_HOST, DEFAULT_PORT, "", ""]]
|
||||
}
|
||||
|
||||
HOSTLIST_COL_ID = 0
|
||||
HOSTLIST_COL_HOST = 1
|
||||
HOSTLIST_COL_PORT = 2
|
||||
HOSTLIST_COL_STATUS = 3
|
||||
HOSTLIST_COL_USER = 4
|
||||
HOSTLIST_COL_PASS = 5
|
||||
HOSTLIST_COL_VERSION = 6
|
||||
|
||||
hostlist = ConfigManager("hostlist.conf.1.2", DEFAULT_HOSTS)
|
||||
|
||||
def rpath(path):
|
||||
"""Convert a relative path into an absolute path relative to the location
|
||||
of this script.
|
||||
"""
|
||||
return os.path.join(current_dir, path)
|
||||
|
||||
class Template(MakoTemplate):
|
||||
|
||||
builtins = {
|
||||
"_": _,
|
||||
"version": common.get_version()
|
||||
}
|
||||
|
||||
def render(self, *args, **data):
|
||||
data.update(self.builtins)
|
||||
return MakoTemplate.render(self, *args, **data)
|
||||
|
||||
class JSONException(Exception):
|
||||
def __init__(self, inner_exception):
|
||||
self.inner_exception = inner_exception
|
||||
Exception.__init__(self, str(inner_exception))
|
||||
|
||||
class JSON(resource.Resource, component.Component):
|
||||
"""
|
||||
A Twisted Web resource that exposes a JSON-RPC interface for web clients
|
||||
to use.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
resource.Resource.__init__(self)
|
||||
component.Component.__init__(self, "JSON")
|
||||
self._remote_methods = []
|
||||
self._local_methods = {
|
||||
"web.update_ui": self.update_ui,
|
||||
"web.download_torrent_from_url": self.download_torrent_from_url,
|
||||
"web.get_torrent_info": self.get_torrent_info,
|
||||
"web.add_torrents": self.add_torrents,
|
||||
"web.login": self.login,
|
||||
"web.get_hosts": self.get_hosts,
|
||||
"web.connect": self.connect,
|
||||
"web.connected": self.connected
|
||||
}
|
||||
|
||||
def _connect(self, host="localhost", port=58846, username="", password=""):
|
||||
"""
|
||||
Connects the client to a daemon
|
||||
"""
|
||||
d = Deferred()
|
||||
_d = client.connect(host, port, username, password)
|
||||
|
||||
def on_get_methods(methods):
|
||||
"""
|
||||
Handles receiving the method names
|
||||
"""
|
||||
self._remote_methods = methods
|
||||
methods = list(self._remote_methods)
|
||||
methods.extend(self._local_methods)
|
||||
d.callback(methods)
|
||||
|
||||
def on_client_connected(connection_id):
|
||||
"""
|
||||
Handles the client successfully connecting to the daemon and
|
||||
invokes retrieving the method names.
|
||||
"""
|
||||
d = client.daemon.get_method_list()
|
||||
d.addCallback(on_get_methods)
|
||||
_d.addCallback(on_client_connected)
|
||||
return d
|
||||
|
||||
def _exec_local(self, method, params):
|
||||
"""
|
||||
Handles executing all local methods.
|
||||
"""
|
||||
if method == "system.listMethods":
|
||||
d = Deferred()
|
||||
methods = list(self._remote_methods)
|
||||
methods.extend(self._local_methods)
|
||||
d.callback(methods)
|
||||
return d
|
||||
elif method in self._local_methods:
|
||||
# This will eventually process methods that the server adds
|
||||
# and any plugins.
|
||||
return self._local_methods[method](*params)
|
||||
raise JSONException("Unknown system method")
|
||||
|
||||
def _exec_remote(self, method, params):
|
||||
"""
|
||||
Executes methods using the Deluge client.
|
||||
"""
|
||||
component, method = method.split(".")
|
||||
return getattr(getattr(client, component), method)(*params)
|
||||
|
||||
def _handle_request(self, request):
|
||||
"""
|
||||
Takes some json data as a string and attempts to decode it, and process
|
||||
the rpc object that should be contained, returning a deferred for all
|
||||
procedure calls and the request id.
|
||||
"""
|
||||
request_id = None
|
||||
try:
|
||||
request = json.loads(request)
|
||||
except ValueError:
|
||||
raise JSONException("JSON not decodable")
|
||||
|
||||
if "method" not in request or "id" not in request or \
|
||||
"params" not in request:
|
||||
raise JSONException("Invalid JSON request")
|
||||
|
||||
method, params = request["method"], request["params"]
|
||||
request_id = request["id"]
|
||||
|
||||
try:
|
||||
if method.startswith("system."):
|
||||
return self._exec_local(method, params), request_id
|
||||
elif method in self._local_methods:
|
||||
return self._exec_local(method, params), request_id
|
||||
elif method in self._remote_methods:
|
||||
return self._exec_remote(method, params), request_id
|
||||
except Exception, e:
|
||||
log.exception(e)
|
||||
d = Deferred()
|
||||
d.callback(None)
|
||||
return d, request_id
|
||||
|
||||
def _on_rpc_request_finished(self, result, response, request):
|
||||
"""
|
||||
Sends the response of any rpc calls back to the json-rpc client.
|
||||
"""
|
||||
response["result"] = result
|
||||
return self._send_response(request, response)
|
||||
|
||||
def _on_rpc_request_failed(self, reason, response, request):
|
||||
"""
|
||||
Handles any failures that occured while making an rpc call.
|
||||
"""
|
||||
print type(reason)
|
||||
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
return ""
|
||||
|
||||
def _on_json_request(self, request):
|
||||
"""
|
||||
Handler to take the json data as a string and pass it on to the
|
||||
_handle_request method for further processing.
|
||||
"""
|
||||
log.debug("json-request: %s", request.json)
|
||||
response = {"result": None, "error": None, "id": None}
|
||||
d, response["id"] = self._handle_request(request.json)
|
||||
d.addCallback(self._on_rpc_request_finished, response, request)
|
||||
d.addErrback(self._on_rpc_request_failed, response, request)
|
||||
return d
|
||||
|
||||
def _on_json_request_failed(self, reason, request):
|
||||
"""
|
||||
Errback handler to return a HTTP code of 500.
|
||||
"""
|
||||
log.exception(reason)
|
||||
request.setResponseCode(http.INTERNAL_SERVER_ERROR)
|
||||
return ""
|
||||
|
||||
def _send_response(self, request, response):
|
||||
response = json.dumps(response)
|
||||
request.setHeader("content-type", "application/x-json")
|
||||
request.write(response)
|
||||
request.finish()
|
||||
|
||||
def render(self, request):
|
||||
"""
|
||||
Handles all the POST requests made to the /json controller.
|
||||
"""
|
||||
|
||||
if request.method != "POST":
|
||||
request.setResponseCode(http.NOT_ALLOWED)
|
||||
return ""
|
||||
|
||||
try:
|
||||
request.content.seek(0)
|
||||
request.json = request.content.read()
|
||||
d = self._on_json_request(request)
|
||||
return server.NOT_DONE_YET
|
||||
except Exception, e:
|
||||
return self._on_json_request_failed(e, request)
|
||||
|
||||
def connect(self, host_id):
|
||||
d = Deferred()
|
||||
def on_connected(methods):
|
||||
d.callback(methods)
|
||||
for host in hostlist["hosts"]:
|
||||
if host_id != host[0]:
|
||||
continue
|
||||
self._connect(*host[1:]).addCallback(on_connected)
|
||||
return d
|
||||
|
||||
def connected(self):
|
||||
d = Deferred()
|
||||
d.callback(client.connected())
|
||||
return d
|
||||
|
||||
def update_ui(self, keys, filter_dict):
|
||||
|
||||
ui_info = {
|
||||
"torrents": None,
|
||||
"filters": None,
|
||||
"stats": None
|
||||
}
|
||||
|
||||
d = Deferred()
|
||||
|
||||
def got_stats(stats):
|
||||
ui_info["stats"] = stats
|
||||
d.callback(ui_info)
|
||||
|
||||
def got_filters(filters):
|
||||
ui_info["filters"] = filters
|
||||
client.core.get_stats().addCallback(got_stats)
|
||||
|
||||
def got_torrents(torrents):
|
||||
ui_info["torrents"] = torrents
|
||||
client.core.get_filter_tree().addCallback(got_filters)
|
||||
client.core.get_torrents_status(filter_dict, keys).addCallback(got_torrents)
|
||||
return d
|
||||
|
||||
def download_torrent_from_url(self, url):
|
||||
"""
|
||||
input:
|
||||
url: the url of the torrent to download
|
||||
|
||||
returns:
|
||||
filename: the temporary file name of the torrent file
|
||||
"""
|
||||
tmp_file = os.path.join(tempfile.gettempdir(), url.split("/")[-1])
|
||||
filename, headers = urllib.urlretrieve(url, tmp_file)
|
||||
log.debug("filename: %s", filename)
|
||||
d = Deferred()
|
||||
d.callback(filename)
|
||||
return d
|
||||
|
||||
def get_torrent_info(self, filename):
|
||||
"""
|
||||
Goal:
|
||||
allow the webui to retrieve data about the torrent
|
||||
|
||||
input:
|
||||
filename: the filename of the torrent to gather info about
|
||||
|
||||
returns:
|
||||
{
|
||||
"filename": the torrent file
|
||||
"name": the torrent name
|
||||
"size": the total size of the torrent
|
||||
"files": the files the torrent contains
|
||||
"info_hash" the torrents info_hash
|
||||
}
|
||||
"""
|
||||
d = Deferred()
|
||||
d.callback(uicommon.get_torrent_info(filename.strip()))
|
||||
return d
|
||||
|
||||
def add_torrents(self, torrents):
|
||||
"""
|
||||
input:
|
||||
torrents [{
|
||||
path: the path of the torrent file,
|
||||
options: the torrent options
|
||||
}]
|
||||
"""
|
||||
for torrent in torrents:
|
||||
filename = os.path.basename(torrent["path"])
|
||||
fdump = open(torrent["path"], "r").read()
|
||||
client.add_torrent_file(filename, fdump, torrent["options"])
|
||||
d = Deferred()
|
||||
d.callback(True)
|
||||
return d
|
||||
|
||||
def login(self, password):
|
||||
"""Method to allow the webui to authenticate
|
||||
"""
|
||||
config = component.get("DelugeWeb").config
|
||||
m = hashlib.md5()
|
||||
m.update(config['pwd_salt'])
|
||||
m.update(password)
|
||||
d = Deferred()
|
||||
d.callback(m.digest() == config['pwd_md5'])
|
||||
return d
|
||||
|
||||
def get_hosts(self):
|
||||
"""Return the hosts in the hostlist"""
|
||||
hosts = dict((host[0], host[:]) for host in hostlist["hosts"])
|
||||
|
||||
main_deferred = Deferred()
|
||||
def run_check():
|
||||
if all(map(lambda x: x[3] is not None, hosts.values())):
|
||||
main_deferred.callback(hosts.values())
|
||||
|
||||
def on_connect(result, c, host_id):
|
||||
def on_info(info, c):
|
||||
hosts[host_id][3] = _("Online")
|
||||
hosts[host_id][4] = info
|
||||
c.disconnect()
|
||||
run_check()
|
||||
|
||||
def on_info_fail(reason):
|
||||
hosts[host_id][3] = _("Offline")
|
||||
run_check()
|
||||
|
||||
d = c.daemon.info()
|
||||
d.addCallback(on_info, c)
|
||||
d.addErrback(on_info_fail, c)
|
||||
|
||||
def on_connect_failed(reason, host_id):
|
||||
log.exception(reason)
|
||||
hosts[host_id][3] = _("Offline")
|
||||
run_check()
|
||||
|
||||
for host in hosts.values():
|
||||
host_id, host, port, user, password = host[0:5]
|
||||
hosts[host_id][3:4] = (None, None)
|
||||
|
||||
if client.connected() and (host, port, user) == client.connection_info():
|
||||
def on_info(info):
|
||||
hosts[host_id][4] = info
|
||||
run_check()
|
||||
host[5] = _("Connected")
|
||||
client.daemon.info().addCallback(on_info)
|
||||
hosts[host_id] = host
|
||||
continue
|
||||
|
||||
c = Client()
|
||||
d = c.connect(host, port, user, password)
|
||||
d.addCallback(on_connect, c, host_id)
|
||||
d.addErrback(on_connect_failed, host_id)
|
||||
return main_deferred
|
||||
|
||||
class GetText(resource.Resource):
|
||||
def render(self, request):
|
||||
request.setHeader("content-type", "text/javascript")
|
||||
|
@ -570,6 +225,7 @@ class DelugeWeb(component.Component):
|
|||
self.site = server.Site(TopLevel())
|
||||
self.config = ConfigManager("web.conf", CONFIG_DEFAULTS)
|
||||
self.port = self.config["port"]
|
||||
self.web_api = WebApi()
|
||||
|
||||
signal.signal(signal.SIGINT, self.shutdown)
|
||||
signal.signal(signal.SIGTERM, self.shutdown)
|
||||
|
|
Loading…
Reference in New Issue