[Tests] [Web] Make JSON independent of Web component

* Implement JSONTestCase in test_json_api.py
* Implement WebAPITestCase test case in test_web_api.py
This commit is contained in:
bendikro 2015-12-15 18:26:55 +01:00
parent bcc1db12e5
commit d58960d723
9 changed files with 634 additions and 164 deletions

View file

@ -86,10 +86,10 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
else:
self.quit_d.errback(status)
def check_callbacks(self, data, type="stdout"):
def check_callbacks(self, data, cb_type="stdout"):
ret = False
for c in self.callbacks:
if type not in c["types"] or c["deferred"].called:
if cb_type not in c["types"] or c["deferred"].called:
continue
for trigger in c["triggers"]:
if trigger["expr"] in data:
@ -118,7 +118,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
"""Process output from stderr"""
self.log_output += data
self.stderr_out += data
self.check_callbacks(data, type="stderr")
self.check_callbacks(data, cb_type="stderr")
if not self.print_stderr:
return
data = "\n%s" % data.strip()

View file

@ -1,3 +1,10 @@
# -*- coding: utf-8 -*-
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
from twisted.internet import defer
import deluge.component as component
@ -78,9 +85,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
return d
def test_connect_no_credentials(self):
d = client.connect(
"localhost", self.listen_port, username="", password=""
)
d = client.connect("localhost", self.listen_port, username="", password="")
def on_connect(result):
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
@ -92,9 +97,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
def test_connect_localclient(self):
username, password = deluge.ui.common.get_localhost_auth()
d = client.connect(
"localhost", self.listen_port, username=username, password=password
)
d = client.connect("localhost", self.listen_port, username=username, password=password)
def on_connect(result):
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
@ -106,15 +109,29 @@ class ClientTestCase(BaseTestCase, DaemonBase):
def test_connect_bad_password(self):
username, password = deluge.ui.common.get_localhost_auth()
d = client.connect(
"localhost", self.listen_port, username=username, password=password + "1"
)
d = client.connect("localhost", self.listen_port, username=username, password=password + "1")
def on_failure(failure):
self.assertEqual(
failure.trap(error.BadLoginError),
error.BadLoginError
)
self.assertEquals(failure.value.message, "Password does not match")
self.addCleanup(client.disconnect)
d.addCallbacks(self.fail, on_failure)
return d
def test_connect_invalid_user(self):
username, password = deluge.ui.common.get_localhost_auth()
d = client.connect("localhost", self.listen_port, username="invalid-user")
def on_failure(failure):
self.assertEqual(
failure.trap(error.BadLoginError),
error.BadLoginError
)
self.assertEquals(failure.value.message, "Username does not exist")
self.addCleanup(client.disconnect)
d.addCallbacks(self.fail, on_failure)
@ -122,9 +139,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
def test_connect_without_password(self):
username, password = deluge.ui.common.get_localhost_auth()
d = client.connect(
"localhost", self.listen_port, username=username
)
d = client.connect("localhost", self.listen_port, username=username)
def on_failure(failure):
self.assertEqual(
@ -137,11 +152,19 @@ class ClientTestCase(BaseTestCase, DaemonBase):
d.addCallbacks(self.fail, on_failure)
return d
@defer.inlineCallbacks
def test_connect_with_password(self):
username, password = deluge.ui.common.get_localhost_auth()
yield client.connect("localhost", self.listen_port, username=username, password=password)
yield client.core.create_account("testuser", "testpw", "DEFAULT")
yield client.disconnect()
ret = yield client.connect("localhost", self.listen_port, username="testuser", password="testpw")
self.assertEquals(ret, deluge.common.AUTH_LEVEL_NORMAL)
yield
@defer.inlineCallbacks
def test_invalid_rpc_method_call(self):
yield client.connect(
"localhost", self.listen_port, username="", password=""
)
yield client.connect("localhost", self.listen_port, username="", password="")
d = client.core.invalid_method()
def on_failure(failure):

View file

@ -0,0 +1,277 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import json as json_lib
from mock import MagicMock
from twisted.internet import defer
from twisted.web import server
from twisted.web.http import Request
import deluge.common
import deluge.component as component
import deluge.ui.web.auth
import deluge.ui.web.json_api
from deluge.error import DelugeError
from deluge.ui.client import client
from deluge.ui.web.auth import Auth
from deluge.ui.web.json_api import JSON, JSONException
from . import common
from .basetest import BaseTestCase
from .daemon_base import DaemonBase
common.disable_new_release_check()
class JSONBase(BaseTestCase, DaemonBase):
def connect_client(self, *args, **kwargs):
return client.connect(
"localhost", self.listen_port, username=kwargs.get("user", ""),
password=kwargs.get("password", "")
)
def disconnect_client(self, *args):
return client.disconnect()
def tear_down(self):
d = component.shutdown()
d.addCallback(self.disconnect_client)
d.addCallback(self.terminate_core)
return d
class JSONTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
d.addCallback(self.start_core)
d.addCallbacks(self.connect_client, self.terminate_core)
return d
@defer.inlineCallbacks
def test_get_remote_methods(self):
json = JSON()
methods = yield json.get_remote_methods()
self.assertEquals(type(methods), tuple)
self.assertTrue(len(methods) > 0)
def test_render_fail_disconnected(self):
json = JSON()
request = MagicMock()
request.method = "POST"
request._disconnected = True
# When disconnected, returns empty string
self.assertEquals(json.render(request), "")
def test_render_fail(self):
json = JSON()
request = MagicMock()
request.method = "POST"
def compress(contents, request):
return contents
self.patch(deluge.ui.web.json_api, "compress", compress)
def write(response_str):
request.write_was_called = True
response = json_lib.loads(response_str)
self.assertEquals(response["result"], None)
self.assertEquals(response["id"], None)
self.assertEquals(response["error"]["message"], "JSONException: JSON not decodable")
self.assertEquals(response["error"]["code"], 5)
request.write = write
request.write_was_called = False
request._disconnected = False
self.assertEquals(json.render(request), server.NOT_DONE_YET)
self.assertTrue(request.write_was_called)
@defer.inlineCallbacks
def test_handle_request_invalid_method(self):
json = JSON()
request = MagicMock()
json_data = {"method": "no-existing-module.test", "id": 0, "params": []}
request.json = json_lib.dumps(json_data)
request_id, result, error = json._handle_request(request)
self.assertEquals(error, {'message': 'Unknown method', 'code': 2})
yield
return
@defer.inlineCallbacks
def test_handle_request_invalid_json_request(self):
json = JSON()
request = MagicMock()
request.json = json_lib.dumps({"id": 0, "params": []})
self.assertRaises(JSONException, json._handle_request, request)
request.json = json_lib.dumps({"method": "some.method", "params": []})
self.assertRaises(JSONException, json._handle_request, request)
request.json = json_lib.dumps({"method": "some.method", "id": 0})
self.assertRaises(JSONException, json._handle_request, request)
yield
class JSONCustomUserTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
d.addCallback(self.start_core)
return d
@defer.inlineCallbacks
def test_handle_request_auth_error(self):
yield self.connect_client()
json = JSON()
auth_conf = {"session_timeout": 10, "sessions": []}
Auth(auth_conf) # Must create the component
# Must be called to update remote methods in json object
yield json.get_remote_methods()
request = MagicMock()
request.getCookie = MagicMock(return_value="bad_value")
json_data = {"method": "core.get_libtorrent_version", "id": 0, "params": []}
request.json = json_lib.dumps(json_data)
request_id, result, error = json._handle_request(request)
self.assertEquals(error, {'message': 'Not authenticated', 'code': 1})
return
class RPCRaiseDelugeErrorJSONTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
custom_script = """
from deluge.error import DelugeError
from deluge.core.rpcserver import export
class TestClass(object):
@export()
def test(self):
raise DelugeError("DelugeERROR")
test = TestClass()
daemon.rpcserver.register_object(test)
"""
d.addCallback(self.start_core, custom_script=custom_script)
d.addCallbacks(self.connect_client, self.terminate_core)
return d
@defer.inlineCallbacks
def test_handle_request_method_raise_delugeerror(self):
json = JSON()
def get_session_id(s_id):
return s_id
self.patch(deluge.ui.web.auth, "get_session_id", get_session_id)
auth_conf = {"session_timeout": 10, "sessions": []}
auth = Auth(auth_conf)
request = Request(MagicMock(), False)
request.base = ""
auth._create_session(request)
methods = yield json.get_remote_methods()
# Verify the function has been registered
self.assertTrue("testclass.test" in methods)
request = MagicMock()
request.getCookie = MagicMock(return_value=auth.config["sessions"].keys()[0])
json_data = {"method": "testclass.test", "id": 0, "params": []}
request.json = json_lib.dumps(json_data)
request_id, result, error = json._handle_request(request)
result.addCallback(self.fail)
def on_error(error):
self.assertEquals(error.type, DelugeError)
result.addErrback(on_error)
yield
return
class JSONRequestFailedTestCase(JSONBase):
def set_up(self):
d = self.common_set_up()
custom_script = """
from deluge.error import DelugeError
from deluge.core.rpcserver import export
from twisted.internet import reactor, task
class TestClass(object):
@export()
def test(self):
def test_raise_error():
raise DelugeError("DelugeERROR")
return task.deferLater(reactor, 1, test_raise_error)
test = TestClass()
daemon.rpcserver.register_object(test)
"""
from twisted.internet.defer import Deferred
extra_callback = {"deferred": Deferred(), "types": ["stderr"],
"timeout": 10,
"triggers": [{"expr": "in test_raise_error",
"value": lambda reader, data, data_all: "Test"}]}
def on_test_raise(*args):
self.assertTrue("in test_raise_error" in self.core.stderr_out)
extra_callback["deferred"].addCallback(on_test_raise)
self.d_test_raise_error_log = extra_callback["deferred"]
d.addCallback(self.start_core, custom_script=custom_script, print_stderr=False,
timeout=5, extra_callbacks=[extra_callback])
d.addCallbacks(self.connect_client, self.terminate_core)
return d
@defer.inlineCallbacks
def test_render_on_rpc_request_failed(self):
json = JSON()
def get_session_id(s_id):
return s_id
self.patch(deluge.ui.web.auth, "get_session_id", get_session_id)
auth_conf = {"session_timeout": 10, "sessions": []}
auth = Auth(auth_conf)
request = Request(MagicMock(), False)
request.base = ""
auth._create_session(request)
methods = yield json.get_remote_methods()
# Verify the function has been registered
self.assertTrue("testclass.test" in methods)
request = MagicMock()
request.getCookie = MagicMock(return_value=auth.config["sessions"].keys()[0])
def compress(contents, request):
return contents
# Patch compress to avoid having to decompress output
self.patch(deluge.ui.web.json_api, "compress", compress)
def write(response_str):
request.write_was_called = True
response = json_lib.loads(response_str)
self.assertEquals(response["result"], None, "BAD RESULT")
self.assertEquals(response["id"], 0)
self.assertEquals(response["error"]["message"],
"Failure: [Failure instance: Traceback (failure with no frames):"
" <class 'deluge.error.DelugeError'>: DelugeERROR\n]"),
self.assertEquals(response["error"]["code"], 4)
request.write = write
request.write_was_called = False
request._disconnected = False
json_data = {"method": "testclass.test", "id": 0, "params": []}
request.json = json_lib.dumps(json_data)
d = json._on_json_request(request)
def on_success(arg):
self.assertEquals(arg, server.NOT_DONE_YET)
return True
d.addCallbacks(on_success, self.fail)
yield d

View file

@ -0,0 +1,223 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#
import os
from StringIO import StringIO
from twisted.internet import defer, reactor
from twisted.python.failure import Failure
from twisted.web.client import Agent, FileBodyProducer
from twisted.web.http_headers import Headers
from twisted.web.static import File
import deluge.common
import deluge.component as component
import deluge.ui.web.auth
import deluge.ui.web.server
from deluge import configmanager
from deluge.ui.client import client
from deluge.ui.web.server import DelugeWeb
from . import common
from .basetest import BaseTestCase
from .daemon_base import DaemonBase
common.disable_new_release_check()
class ReactorOverride(object):
def __getattr__(self, attr):
if attr == "run":
return self._run
if attr == "stop":
return self._stop
return getattr(reactor, attr)
def _run(self):
pass
def _stop(self):
pass
class WebAPITestCase(BaseTestCase, DaemonBase):
def set_up(self):
self.host_id = None
deluge.ui.web.server.reactor = ReactorOverride()
d = self.common_set_up()
d.addCallback(self.start_core)
d.addCallback(self.start_webapi)
return d
def start_webapi(self, arg):
self.webserver_listen_port = 8999
config_defaults = deluge.ui.web.server.CONFIG_DEFAULTS.copy()
config_defaults["port"] = self.webserver_listen_port
self.config = configmanager.ConfigManager("web.conf", config_defaults)
self.deluge_web = DelugeWeb()
host = list(self.deluge_web.web_api.host_list["hosts"][0])
host[2] = self.listen_port
self.deluge_web.web_api.host_list["hosts"][0] = tuple(host)
self.host_id = host[0]
self.deluge_web.start()
def tear_down(self):
d = component.shutdown()
d.addCallback(self.terminate_core)
return d
def test_connect_invalid_host(self):
d = self.deluge_web.web_api.connect("id")
d.addCallback(self.fail)
d.addErrback(self.assertIsInstance, Failure)
return d
def test_connect(self):
d = self.deluge_web.web_api.connect(self.host_id)
def on_connect(result):
self.assertEquals(type(result), tuple)
self.assertTrue(len(result) > 0)
self.addCleanup(client.disconnect)
return result
d.addCallback(on_connect)
d.addErrback(self.fail)
return d
def test_disconnect(self):
d = self.deluge_web.web_api.connect(self.host_id)
@defer.inlineCallbacks
def on_connect(result):
self.assertTrue(self.deluge_web.web_api.connected())
yield self.deluge_web.web_api.disconnect()
self.assertFalse(self.deluge_web.web_api.connected())
d.addCallback(on_connect)
d.addErrback(self.fail)
return d
def test_get_config(self):
config = self.deluge_web.web_api.get_config()
self.assertEquals(self.webserver_listen_port, config["port"])
def test_set_config(self):
config = self.deluge_web.web_api.get_config()
config["pwd_salt"] = "new_salt"
config["pwd_sha1"] = 'new_sha'
config["sessions"] = {
"233f23632af0a74748bc5dd1d8717564748877baa16420e6898e17e8aa365e6e": {
"login": "skrot",
"expires": 1460030877.0,
"level": 10
}
}
self.deluge_web.web_api.set_config(config)
web_config = component.get("DelugeWeb").config.config
self.assertNotEquals(config["pwd_salt"], web_config["pwd_salt"])
self.assertNotEquals(config["pwd_sha1"], web_config["pwd_sha1"])
self.assertNotEquals(config["sessions"], web_config["sessions"])
@defer.inlineCallbacks
def get_host_status(self):
host = list(self.deluge_web.web_api._get_host(self.host_id))
host[3] = 'Online'
host[4] = u'2.0.0.dev562'
status = yield self.deluge_web.web_api.get_host_status(self.host_id)
self.assertEquals(status, tuple(status))
def test_get_host(self):
self.assertEquals(self.deluge_web.web_api._get_host("invalid_id"), None)
conn = self.deluge_web.web_api.host_list["hosts"][0]
self.assertEquals(self.deluge_web.web_api._get_host(conn[0]), conn)
def test_add_host(self):
conn = [None, '', 0, '', '']
self.assertEquals(self.deluge_web.web_api._get_host(conn[0]), None)
# Add valid host
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4])
self.assertEquals(ret[0], True)
conn[0] = ret[1]
self.assertEquals(self.deluge_web.web_api._get_host(conn[0]), conn)
# Add already existing host
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4])
self.assertEquals(ret, (False, "Host already in the list"))
# Add invalid port
conn[2] = "bad port"
ret = self.deluge_web.web_api.add_host(conn[1], conn[2], conn[3], conn[4])
self.assertEquals(ret, (False, "Port is invalid"))
def test_remove_host(self):
conn = ['connection_id', '', 0, '', '']
self.deluge_web.web_api.host_list["hosts"].append(conn)
self.assertEquals(self.deluge_web.web_api._get_host(conn[0]), conn)
# Remove valid host
self.assertTrue(self.deluge_web.web_api.remove_host(conn[0]))
self.assertEquals(self.deluge_web.web_api._get_host(conn[0]), None)
# Remove non-existing host
self.assertFalse(self.deluge_web.web_api.remove_host(conn[0]))
def test_get_torrent_info(self):
filename = os.path.join(os.path.dirname(__file__), "test.torrent")
ret = self.deluge_web.web_api.get_torrent_info(filename)
self.assertEquals(ret["name"], "azcvsupdater_2.6.2.jar")
self.assertEquals(ret["info_hash"], "ab570cdd5a17ea1b61e970bb72047de141bce173")
self.assertTrue("files_tree" in ret)
def test_get_magnet_info(self):
ret = self.deluge_web.web_api.get_magnet_info("magnet:?xt=urn:btih:SU5225URMTUEQLDXQWRB2EQWN6KLTYKN")
self.assertEquals(ret["name"], "953bad769164e8482c7785a21d12166f94b9e14d")
self.assertEquals(ret["info_hash"], "953bad769164e8482c7785a21d12166f94b9e14d")
self.assertTrue("files_tree" in ret)
@defer.inlineCallbacks
def test_get_torrent_files(self):
yield self.deluge_web.web_api.connect(self.host_id)
filename = os.path.join(os.path.dirname(__file__), "test.torrent")
torrents = [{"path": filename, "options": {"download_location": "/home/deluge/"}}]
self.deluge_web.web_api.add_torrents(torrents)
ret = yield self.deluge_web.web_api.get_torrent_files("ab570cdd5a17ea1b61e970bb72047de141bce173")
self.assertEquals(ret["type"], "dir")
self.assertEquals(ret["contents"], {u'azcvsupdater_2.6.2.jar':
{'priority': 1, u'index': 0, u'offset': 0, 'progress': 0.0, u'path':
u'azcvsupdater_2.6.2.jar', 'type': 'file', u'size': 307949}})
@defer.inlineCallbacks
def test_download_torrent_from_url(self):
filename = "ubuntu-9.04-desktop-i386.iso.torrent"
self.deluge_web.top_level.putChild(filename, File(common.rpath(filename)))
url = "http://localhost:%d/%s" % (self.webserver_listen_port, filename)
res = yield self.deluge_web.web_api.download_torrent_from_url(url)
self.assertTrue(res.endswith(filename))
@defer.inlineCallbacks
def test_invalid_json(self):
"""
If json_api._send_response does not return server.NOT_DONE_YET
this error is thrown when json is invalid:
exceptions.RuntimeError: Request.write called on a request after Request.finish was called.
"""
agent = Agent(reactor)
bad_body = '{ method": "auth.login" }'
d = yield agent.request(
'POST',
'http://127.0.0.1:%s/json' % self.webserver_listen_port,
Headers({'User-Agent': ['Twisted Web Client Example'],
'Content-Type': ['application/json']}),
FileBodyProducer(StringIO(bad_body)))
yield d

View file

@ -16,7 +16,6 @@ from email.utils import formatdate
from twisted.internet.task import LoopingCall
from deluge import component
from deluge.common import utf8_encoded
log = logging.getLogger(__name__)
@ -79,9 +78,10 @@ class Auth(JSONComponent):
The component that implements authentification into the JSON interface.
"""
def __init__(self):
def __init__(self, config):
super(Auth, self).__init__("Auth")
self.worker = LoopingCall(self._clean_sessions)
self.config = config
def start(self):
self.worker.start(5)
@ -90,19 +90,18 @@ class Auth(JSONComponent):
self.worker.stop()
def _clean_sessions(self):
config = component.get("DelugeWeb").config
session_ids = config["sessions"].keys()
session_ids = self.config["sessions"].keys()
now = time.gmtime()
for session_id in session_ids:
session = config["sessions"][session_id]
session = self.config["sessions"][session_id]
if "expires" not in session:
del config["sessions"][session_id]
del self.config["sessions"][session_id]
continue
if time.gmtime(session["expires"]) < now:
del config["sessions"][session_id]
del self.config["sessions"][session_id]
continue
def _create_session(self, request, login='admin'):
@ -117,21 +116,18 @@ class Auth(JSONComponent):
m.update(os.urandom(32))
session_id = m.hexdigest()
config = component.get("DelugeWeb").config
expires, expires_str = make_expires(config["session_timeout"])
expires, expires_str = make_expires(self.config["session_timeout"])
checksum = str(make_checksum(session_id))
request.addCookie('_session_id', session_id + checksum,
path=request.base + "json", expires=expires_str)
log.debug("Creating session for %s", login)
config = component.get("DelugeWeb").config
if isinstance(config["sessions"], list):
config.config["sessions"] = {}
if isinstance(self.config["sessions"], list):
self.config["sessions"] = {}
config["sessions"][session_id] = {
self.config["sessions"][session_id] = {
"login": login,
"level": AUTH_LEVEL_ADMIN,
"expires": expires
@ -139,7 +135,7 @@ class Auth(JSONComponent):
return True
def check_password(self, password):
config = component.get("DelugeWeb").config
config = self.config
if "pwd_md5" in config.config:
# We are using the 1.2-dev auth method
log.debug("Received a password via the 1.2-dev auth method")
@ -206,16 +202,15 @@ class Auth(JSONComponent):
:raises: Exception
"""
config = component.get("DelugeWeb").config
session_id = get_session_id(request.getCookie("_session_id"))
if session_id not in config["sessions"]:
if session_id not in self.config["sessions"]:
auth_level = AUTH_LEVEL_NONE
session_id = None
else:
session = config["sessions"][session_id]
session = self.config["sessions"][session_id]
auth_level = session["level"]
expires, expires_str = make_expires(config["session_timeout"])
expires, expires_str = make_expires(self.config["session_timeout"])
session["expires"] = expires
_session_id = request.getCookie("_session_id")
@ -253,9 +248,8 @@ class Auth(JSONComponent):
salt = hashlib.sha1(os.urandom(32)).hexdigest()
s = hashlib.sha1(salt)
s.update(utf8_encoded(new_password))
config = component.get("DelugeWeb").config
config["pwd_salt"] = salt
config["pwd_sha1"] = s.hexdigest()
self.config["pwd_salt"] = salt
self.config["pwd_sha1"] = s.hexdigest()
return True
@export
@ -290,8 +284,7 @@ class Auth(JSONComponent):
:param session_id: the id for the session to remove
:type session_id: string
"""
config = component.get("DelugeWeb").config
del config["sessions"][__request__.session_id]
del self.config["sessions"][__request__.session_id]
return True
@export(AUTH_LEVEL_NONE)

View file

@ -16,14 +16,12 @@ import shutil
import tempfile
import time
from types import FunctionType
from urllib import unquote_plus
from twisted.internet import defer, reactor
from twisted.internet.defer import Deferred, DeferredList
from twisted.web import http, resource, server
from deluge import component, httpdownloader
from deluge.common import is_magnet
from deluge import common, component, httpdownloader
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.ui import common as uicommon
from deluge.ui.client import Client, client
@ -104,47 +102,6 @@ class JSON(resource.Resource, component.Component):
return methods
return client.daemon.get_method_list().addCallback(on_get_methods)
def connect(self, host="localhost", port=58846, username="", password=""):
"""
Connects the client to a daemon
"""
d = client.connect(host, port, username, password)
def on_client_connected(connection_id):
"""
Handles the client successfully connecting to the daemon and
invokes retrieving the method names.
"""
d = self.get_remote_methods()
component.get("Web.PluginManager").start()
component.get("Web").start()
return d
return d.addCallback(on_client_connected)
def disable(self):
if not client.is_classicmode():
client.disconnect()
client.set_disconnect_callback(None)
def enable(self):
if not client.is_classicmode():
client.set_disconnect_callback(self._on_client_disconnect)
client.register_event_handler("PluginEnabledEvent", self.get_remote_methods)
client.register_event_handler("PluginDisabledEvent", self.get_remote_methods)
if component.get("DelugeWeb").config["default_daemon"]:
# Sort out getting the default daemon here
default = component.get("DelugeWeb").config["default_daemon"]
host = component.get("Web").get_host(default)
if host:
self.connect(*host[1:])
else:
self.connect()
def _on_client_disconnect(self, *args):
component.get("Web.PluginManager").stop()
component.get("Web").stop()
def _exec_local(self, method, params, request):
"""
Handles executing all local methods.
@ -180,9 +137,8 @@ class JSON(resource.Resource, component.Component):
"""
try:
request.json = json.loads(request.json)
except ValueError:
except (ValueError, TypeError):
raise JSONException("JSON not decodable")
if "method" not in request.json or "id" not in request.json or \
"params" not in request.json:
raise JSONException("Invalid JSON request")
@ -257,12 +213,12 @@ class JSON(resource.Resource, component.Component):
request.setHeader("content-type", "application/x-json")
request.write(compress(response, request))
request.finish()
return server.NOT_DONE_YET
def render(self, request):
"""
Handles all the POST requests made to the /json controller.
"""
if request.method != "POST":
request.setResponseCode(http.NOT_ALLOWED)
request.finish()
@ -421,7 +377,30 @@ class WebApi(JSONComponent):
except KeyError:
self.sessionproxy = SessionProxy()
def get_host(self, host_id):
def disable(self):
if not client.is_classicmode():
client.disconnect()
client.set_disconnect_callback(None)
def enable(self):
if not client.is_classicmode():
client.set_disconnect_callback(self._on_client_disconnect)
client.register_event_handler("PluginEnabledEvent", self._json.get_remote_methods)
client.register_event_handler("PluginDisabledEvent", self._json.get_remote_methods)
if component.get("DelugeWeb").config["default_daemon"]:
# Sort out getting the default daemon here
default = component.get("DelugeWeb").config["default_daemon"]
host = component.get("Web")._get_host(default)
if host:
self._connect_daemon(*host[1:])
else:
self._connect_daemon()
def _on_client_disconnect(self, *args):
component.get("Web.PluginManager").stop()
self.stop()
def _get_host(self, host_id):
"""
Return the information about a host
@ -442,6 +421,24 @@ class WebApi(JSONComponent):
self.core_config.stop()
self.sessionproxy.stop()
def _connect_daemon(self, host="localhost", port=58846, username="", password=""):
"""
Connects the client to a daemon
"""
d = client.connect(host, port, username, password)
def on_client_connected(connection_id):
"""
Handles the client successfully connecting to the daemon and
invokes retrieving the method names.
"""
d = self._json.get_remote_methods()
component.get("Web.PluginManager").start()
self.start()
return d
return d.addCallback(on_client_connected)
@export
def connect(self, host_id):
"""
@ -452,16 +449,10 @@ class WebApi(JSONComponent):
:returns: the methods the daemon supports
:rtype: list
"""
d = Deferred()
def on_connected(methods):
d.callback(methods)
host = self.get_host(host_id)
host = self._get_host(host_id)
if host:
self._json.connect(*host[1:]).addCallback(on_connected)
else:
return defer.fail(Exception("Bad host id"))
return d
return self._connect_daemon(*host[1:])
return defer.fail(Exception("Bad host id"))
@export
def connected(self):
@ -478,8 +469,12 @@ class WebApi(JSONComponent):
"""
Disconnect the web interface from the connected daemon.
"""
client.disconnect()
return True
d = client.disconnect()
def on_disconnect(reason):
return str(reason)
d.addCallback(on_disconnect)
return d
@export
def update_ui(self, keys, filter_dict):
@ -677,47 +672,7 @@ class WebApi(JSONComponent):
@export
def get_magnet_info(self, uri):
"""
Return information about a magnet link.
:param uri: the magnet link
:type uri: string
:returns: information about the magnet link:
::
{
"name": the torrent name,
"info_hash": the torrents info_hash,
"files_tree": empty value for magnet links
}
:rtype: dictionary
"""
magnet_scheme = 'magnet:?'
xt_param = 'xt=urn:btih:'
dn_param = 'dn='
if uri.startswith(magnet_scheme):
name = None
info_hash = None
for param in uri[len(magnet_scheme):].split('&'):
if param.startswith(xt_param):
xt_hash = param[len(xt_param):]
if len(xt_hash) == 32:
info_hash = base64.b32decode(xt_hash).encode("hex")
elif len(xt_hash) == 40:
info_hash = xt_hash
else:
break
elif param.startswith(dn_param):
name = unquote_plus(param[len(dn_param):])
if info_hash:
if not name:
name = info_hash
return {"name": name, "info_hash": info_hash, "files_tree": ''}
return False
return common.get_magnet_info(uri)
@export
def add_torrents(self, torrents):
@ -737,7 +692,7 @@ class WebApi(JSONComponent):
"""
for torrent in torrents:
if is_magnet(torrent["path"]):
if common.is_magnet(torrent["path"]):
log.info("Adding torrent from magnet uri `%s` with options `%r`",
torrent["path"], torrent["options"])
client.core.add_torrent_magnet(torrent["path"], torrent["options"])
@ -769,7 +724,7 @@ class WebApi(JSONComponent):
return host_id, host, port, status, info
try:
host_id, host, port, user, password = self.get_host(host_id)
host_id, host, port, user, password = self._get_host(host_id)
except TypeError:
host = None
port = None
@ -808,8 +763,8 @@ class WebApi(JSONComponent):
@export
def start_daemon(self, port):
"""
Starts a local daemon.
"""
Starts a local daemon.
"""
client.start_daemon(port, get_config_dir())
@export
@ -821,7 +776,7 @@ class WebApi(JSONComponent):
:type host_id: string
"""
main_deferred = Deferred()
host = self.get_host(host_id)
host = self._get_host(host_id)
if not host:
main_deferred.callback((False, _("Daemon doesn't exist")))
return main_deferred
@ -864,7 +819,7 @@ class WebApi(JSONComponent):
# Check to see if there is already an entry for this host and return
# if thats the case
for entry in self.host_list["hosts"]:
if (entry[0], entry[1], entry[2]) == (host, port, username):
if (entry[1], entry[2], entry[3]) == (host, port, username):
return (False, "Host already in the list")
try:
@ -877,7 +832,7 @@ class WebApi(JSONComponent):
self.host_list["hosts"].append([connection_id, host, port, username,
password])
self.host_list.save()
return (True,)
return True, connection_id
@export
def remove_host(self, connection_id):
@ -887,7 +842,7 @@ class WebApi(JSONComponent):
:param host_id: the hash id of the host
:type host_id: string
"""
host = self.get_host(connection_id)
host = self._get_host(connection_id)
if host is None:
return False
@ -919,6 +874,9 @@ class WebApi(JSONComponent):
"""
web_config = component.get("DelugeWeb").config
for key in config.keys():
if key in ["sessions", "pwd_salt", "pwd_sha1"]:
log.warn("Ignored attempt to overwrite web config key '%s'" % key)
continue
if isinstance(config[key], basestring):
config[key] = config[key].encode("utf8")
web_config[key] = config[key]

View file

@ -16,7 +16,7 @@ import tempfile
from OpenSSL.crypto import FILETYPE_PEM
from twisted.application import internet, service
from twisted.internet import defer, error, reactor
from twisted.internet import defer, reactor
from twisted.internet.ssl import SSL, Certificate, CertificateOptions, KeyPair
from twisted.web import http, resource, server, static
@ -533,7 +533,6 @@ class DelugeWeb(component.Component):
def __init__(self):
super(DelugeWeb, self).__init__("DelugeWeb")
self.config = configmanager.ConfigManager("web.conf", CONFIG_DEFAULTS)
self.socket = None
self.top_level = TopLevel()
self.site = server.Site(self.top_level)
@ -544,7 +543,7 @@ class DelugeWeb(component.Component):
self.cert = self.config["cert"]
self.base = self.config["base"]
self.web_api = WebApi()
self.auth = Auth()
self.auth = Auth(self.config)
# Initalize the plugins
self.plugins = PluginManager()
@ -568,17 +567,15 @@ class DelugeWeb(component.Component):
return 1
SetConsoleCtrlHandler(win_handler)
def start(self, start_reactor=True):
def start(self):
log.info("%s %s.", _("Starting server in PID"), os.getpid())
if self.https:
self.start_ssl()
else:
self.start_normal()
component.get("JSON").enable()
if start_reactor:
reactor.run()
component.get("Web").enable()
reactor.run()
def start_normal(self):
self.socket = reactor.listenTCP(self.port, self.site, interface=self.interface)
@ -600,7 +597,7 @@ class DelugeWeb(component.Component):
def stop(self):
log.info("Shutting down webserver")
component.get("JSON").disable()
component.get("Web").disable()
self.plugins.disable_plugins()
log.debug("Saving configuration file")
@ -616,10 +613,8 @@ class DelugeWeb(component.Component):
def shutdown(self, *args):
self.stop()
try:
reactor.stop()
except error.ReactorNotRunning:
log.debug("Reactor not running")
reactor.stop()
if __name__ == "__builtin__":
deluge_web = DelugeWeb()

View file

@ -16,7 +16,7 @@ frameworks = CoreFoundation, Foundation, AppKit
[isort]
known_standard_library=unicodedata
known_third_party=pygtk,gtk,gobject,gtk.gdk,pango,cairo,pangocairo,twisted,pytest,OpenSSL,pkg_resources,chardet,bbfreeze,win32verstamp
known_third_party=pygtk,gtk,gobject,gtk.gdk,pango,cairo,pangocairo,twisted,pytest,OpenSSL,pkg_resources,chardet,bbfreeze,win32verstamp,mock
known_first_party=msgfmt
order_by_type=true
line_length=120

View file

@ -28,6 +28,7 @@ deps =
pyopenssl
pyxdg
pytest
mock
whitelist_externals = py.test
commands = {envpython} setup.py test