mirror of
https://git.deluge-torrent.org/deluge
synced 2025-08-05 16:08:40 +00:00
[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:
parent
bcc1db12e5
commit
d58960d723
9 changed files with 634 additions and 164 deletions
|
@ -86,10 +86,10 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
|
||||||
else:
|
else:
|
||||||
self.quit_d.errback(status)
|
self.quit_d.errback(status)
|
||||||
|
|
||||||
def check_callbacks(self, data, type="stdout"):
|
def check_callbacks(self, data, cb_type="stdout"):
|
||||||
ret = False
|
ret = False
|
||||||
for c in self.callbacks:
|
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
|
continue
|
||||||
for trigger in c["triggers"]:
|
for trigger in c["triggers"]:
|
||||||
if trigger["expr"] in data:
|
if trigger["expr"] in data:
|
||||||
|
@ -118,7 +118,7 @@ class ProcessOutputHandler(protocol.ProcessProtocol):
|
||||||
"""Process output from stderr"""
|
"""Process output from stderr"""
|
||||||
self.log_output += data
|
self.log_output += data
|
||||||
self.stderr_out += data
|
self.stderr_out += data
|
||||||
self.check_callbacks(data, type="stderr")
|
self.check_callbacks(data, cb_type="stderr")
|
||||||
if not self.print_stderr:
|
if not self.print_stderr:
|
||||||
return
|
return
|
||||||
data = "\n%s" % data.strip()
|
data = "\n%s" % data.strip()
|
||||||
|
|
|
@ -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
|
from twisted.internet import defer
|
||||||
|
|
||||||
import deluge.component as component
|
import deluge.component as component
|
||||||
|
@ -78,9 +85,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_connect_no_credentials(self):
|
def test_connect_no_credentials(self):
|
||||||
d = client.connect(
|
d = client.connect("localhost", self.listen_port, username="", password="")
|
||||||
"localhost", self.listen_port, username="", password=""
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_connect(result):
|
def on_connect(result):
|
||||||
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
|
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
|
||||||
|
@ -92,9 +97,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
||||||
|
|
||||||
def test_connect_localclient(self):
|
def test_connect_localclient(self):
|
||||||
username, password = deluge.ui.common.get_localhost_auth()
|
username, password = deluge.ui.common.get_localhost_auth()
|
||||||
d = client.connect(
|
d = client.connect("localhost", self.listen_port, username=username, password=password)
|
||||||
"localhost", self.listen_port, username=username, password=password
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_connect(result):
|
def on_connect(result):
|
||||||
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
|
self.assertEqual(client.get_auth_level(), AUTH_LEVEL_ADMIN)
|
||||||
|
@ -106,15 +109,29 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
||||||
|
|
||||||
def test_connect_bad_password(self):
|
def test_connect_bad_password(self):
|
||||||
username, password = deluge.ui.common.get_localhost_auth()
|
username, password = deluge.ui.common.get_localhost_auth()
|
||||||
d = client.connect(
|
d = client.connect("localhost", self.listen_port, username=username, password=password + "1")
|
||||||
"localhost", self.listen_port, username=username, password=password + "1"
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_failure(failure):
|
def on_failure(failure):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
failure.trap(error.BadLoginError),
|
failure.trap(error.BadLoginError),
|
||||||
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)
|
self.addCleanup(client.disconnect)
|
||||||
|
|
||||||
d.addCallbacks(self.fail, on_failure)
|
d.addCallbacks(self.fail, on_failure)
|
||||||
|
@ -122,9 +139,7 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
||||||
|
|
||||||
def test_connect_without_password(self):
|
def test_connect_without_password(self):
|
||||||
username, password = deluge.ui.common.get_localhost_auth()
|
username, password = deluge.ui.common.get_localhost_auth()
|
||||||
d = client.connect(
|
d = client.connect("localhost", self.listen_port, username=username)
|
||||||
"localhost", self.listen_port, username=username
|
|
||||||
)
|
|
||||||
|
|
||||||
def on_failure(failure):
|
def on_failure(failure):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
@ -137,11 +152,19 @@ class ClientTestCase(BaseTestCase, DaemonBase):
|
||||||
d.addCallbacks(self.fail, on_failure)
|
d.addCallbacks(self.fail, on_failure)
|
||||||
return d
|
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
|
@defer.inlineCallbacks
|
||||||
def test_invalid_rpc_method_call(self):
|
def test_invalid_rpc_method_call(self):
|
||||||
yield client.connect(
|
yield client.connect("localhost", self.listen_port, username="", password="")
|
||||||
"localhost", self.listen_port, username="", password=""
|
|
||||||
)
|
|
||||||
d = client.core.invalid_method()
|
d = client.core.invalid_method()
|
||||||
|
|
||||||
def on_failure(failure):
|
def on_failure(failure):
|
||||||
|
|
277
deluge/tests/test_json_api.py
Normal file
277
deluge/tests/test_json_api.py
Normal 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
|
223
deluge/tests/test_web_api.py
Normal file
223
deluge/tests/test_web_api.py
Normal 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
|
|
@ -16,7 +16,6 @@ from email.utils import formatdate
|
||||||
|
|
||||||
from twisted.internet.task import LoopingCall
|
from twisted.internet.task import LoopingCall
|
||||||
|
|
||||||
from deluge import component
|
|
||||||
from deluge.common import utf8_encoded
|
from deluge.common import utf8_encoded
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
@ -79,9 +78,10 @@ class Auth(JSONComponent):
|
||||||
The component that implements authentification into the JSON interface.
|
The component that implements authentification into the JSON interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, config):
|
||||||
super(Auth, self).__init__("Auth")
|
super(Auth, self).__init__("Auth")
|
||||||
self.worker = LoopingCall(self._clean_sessions)
|
self.worker = LoopingCall(self._clean_sessions)
|
||||||
|
self.config = config
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.worker.start(5)
|
self.worker.start(5)
|
||||||
|
@ -90,19 +90,18 @@ class Auth(JSONComponent):
|
||||||
self.worker.stop()
|
self.worker.stop()
|
||||||
|
|
||||||
def _clean_sessions(self):
|
def _clean_sessions(self):
|
||||||
config = component.get("DelugeWeb").config
|
session_ids = self.config["sessions"].keys()
|
||||||
session_ids = config["sessions"].keys()
|
|
||||||
|
|
||||||
now = time.gmtime()
|
now = time.gmtime()
|
||||||
for session_id in session_ids:
|
for session_id in session_ids:
|
||||||
session = config["sessions"][session_id]
|
session = self.config["sessions"][session_id]
|
||||||
|
|
||||||
if "expires" not in session:
|
if "expires" not in session:
|
||||||
del config["sessions"][session_id]
|
del self.config["sessions"][session_id]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if time.gmtime(session["expires"]) < now:
|
if time.gmtime(session["expires"]) < now:
|
||||||
del config["sessions"][session_id]
|
del self.config["sessions"][session_id]
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def _create_session(self, request, login='admin'):
|
def _create_session(self, request, login='admin'):
|
||||||
|
@ -117,21 +116,18 @@ class Auth(JSONComponent):
|
||||||
m.update(os.urandom(32))
|
m.update(os.urandom(32))
|
||||||
session_id = m.hexdigest()
|
session_id = m.hexdigest()
|
||||||
|
|
||||||
config = component.get("DelugeWeb").config
|
expires, expires_str = make_expires(self.config["session_timeout"])
|
||||||
|
|
||||||
expires, expires_str = make_expires(config["session_timeout"])
|
|
||||||
checksum = str(make_checksum(session_id))
|
checksum = str(make_checksum(session_id))
|
||||||
|
|
||||||
request.addCookie('_session_id', session_id + checksum,
|
request.addCookie('_session_id', session_id + checksum,
|
||||||
path=request.base + "json", expires=expires_str)
|
path=request.base + "json", expires=expires_str)
|
||||||
|
|
||||||
log.debug("Creating session for %s", login)
|
log.debug("Creating session for %s", login)
|
||||||
config = component.get("DelugeWeb").config
|
|
||||||
|
|
||||||
if isinstance(config["sessions"], list):
|
if isinstance(self.config["sessions"], list):
|
||||||
config.config["sessions"] = {}
|
self.config["sessions"] = {}
|
||||||
|
|
||||||
config["sessions"][session_id] = {
|
self.config["sessions"][session_id] = {
|
||||||
"login": login,
|
"login": login,
|
||||||
"level": AUTH_LEVEL_ADMIN,
|
"level": AUTH_LEVEL_ADMIN,
|
||||||
"expires": expires
|
"expires": expires
|
||||||
|
@ -139,7 +135,7 @@ class Auth(JSONComponent):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
config = component.get("DelugeWeb").config
|
config = self.config
|
||||||
if "pwd_md5" in config.config:
|
if "pwd_md5" in config.config:
|
||||||
# We are using the 1.2-dev auth method
|
# We are using the 1.2-dev auth method
|
||||||
log.debug("Received a password via 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
|
:raises: Exception
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config = component.get("DelugeWeb").config
|
|
||||||
session_id = get_session_id(request.getCookie("_session_id"))
|
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
|
auth_level = AUTH_LEVEL_NONE
|
||||||
session_id = None
|
session_id = None
|
||||||
else:
|
else:
|
||||||
session = config["sessions"][session_id]
|
session = self.config["sessions"][session_id]
|
||||||
auth_level = session["level"]
|
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["expires"] = expires
|
||||||
|
|
||||||
_session_id = request.getCookie("_session_id")
|
_session_id = request.getCookie("_session_id")
|
||||||
|
@ -253,9 +248,8 @@ class Auth(JSONComponent):
|
||||||
salt = hashlib.sha1(os.urandom(32)).hexdigest()
|
salt = hashlib.sha1(os.urandom(32)).hexdigest()
|
||||||
s = hashlib.sha1(salt)
|
s = hashlib.sha1(salt)
|
||||||
s.update(utf8_encoded(new_password))
|
s.update(utf8_encoded(new_password))
|
||||||
config = component.get("DelugeWeb").config
|
self.config["pwd_salt"] = salt
|
||||||
config["pwd_salt"] = salt
|
self.config["pwd_sha1"] = s.hexdigest()
|
||||||
config["pwd_sha1"] = s.hexdigest()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@export
|
@export
|
||||||
|
@ -290,8 +284,7 @@ class Auth(JSONComponent):
|
||||||
:param session_id: the id for the session to remove
|
:param session_id: the id for the session to remove
|
||||||
:type session_id: string
|
:type session_id: string
|
||||||
"""
|
"""
|
||||||
config = component.get("DelugeWeb").config
|
del self.config["sessions"][__request__.session_id]
|
||||||
del config["sessions"][__request__.session_id]
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@export(AUTH_LEVEL_NONE)
|
@export(AUTH_LEVEL_NONE)
|
||||||
|
|
|
@ -16,14 +16,12 @@ import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
from urllib import unquote_plus
|
|
||||||
|
|
||||||
from twisted.internet import defer, reactor
|
from twisted.internet import defer, reactor
|
||||||
from twisted.internet.defer import Deferred, DeferredList
|
from twisted.internet.defer import Deferred, DeferredList
|
||||||
from twisted.web import http, resource, server
|
from twisted.web import http, resource, server
|
||||||
|
|
||||||
from deluge import component, httpdownloader
|
from deluge import common, component, httpdownloader
|
||||||
from deluge.common import is_magnet
|
|
||||||
from deluge.configmanager import ConfigManager, get_config_dir
|
from deluge.configmanager import ConfigManager, get_config_dir
|
||||||
from deluge.ui import common as uicommon
|
from deluge.ui import common as uicommon
|
||||||
from deluge.ui.client import Client, client
|
from deluge.ui.client import Client, client
|
||||||
|
@ -104,47 +102,6 @@ class JSON(resource.Resource, component.Component):
|
||||||
return methods
|
return methods
|
||||||
return client.daemon.get_method_list().addCallback(on_get_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):
|
def _exec_local(self, method, params, request):
|
||||||
"""
|
"""
|
||||||
Handles executing all local methods.
|
Handles executing all local methods.
|
||||||
|
@ -180,9 +137,8 @@ class JSON(resource.Resource, component.Component):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
request.json = json.loads(request.json)
|
request.json = json.loads(request.json)
|
||||||
except ValueError:
|
except (ValueError, TypeError):
|
||||||
raise JSONException("JSON not decodable")
|
raise JSONException("JSON not decodable")
|
||||||
|
|
||||||
if "method" not in request.json or "id" not in request.json or \
|
if "method" not in request.json or "id" not in request.json or \
|
||||||
"params" not in request.json:
|
"params" not in request.json:
|
||||||
raise JSONException("Invalid JSON request")
|
raise JSONException("Invalid JSON request")
|
||||||
|
@ -257,12 +213,12 @@ class JSON(resource.Resource, component.Component):
|
||||||
request.setHeader("content-type", "application/x-json")
|
request.setHeader("content-type", "application/x-json")
|
||||||
request.write(compress(response, request))
|
request.write(compress(response, request))
|
||||||
request.finish()
|
request.finish()
|
||||||
|
return server.NOT_DONE_YET
|
||||||
|
|
||||||
def render(self, request):
|
def render(self, request):
|
||||||
"""
|
"""
|
||||||
Handles all the POST requests made to the /json controller.
|
Handles all the POST requests made to the /json controller.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if request.method != "POST":
|
if request.method != "POST":
|
||||||
request.setResponseCode(http.NOT_ALLOWED)
|
request.setResponseCode(http.NOT_ALLOWED)
|
||||||
request.finish()
|
request.finish()
|
||||||
|
@ -421,7 +377,30 @@ class WebApi(JSONComponent):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.sessionproxy = SessionProxy()
|
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
|
Return the information about a host
|
||||||
|
|
||||||
|
@ -442,6 +421,24 @@ class WebApi(JSONComponent):
|
||||||
self.core_config.stop()
|
self.core_config.stop()
|
||||||
self.sessionproxy.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
|
@export
|
||||||
def connect(self, host_id):
|
def connect(self, host_id):
|
||||||
"""
|
"""
|
||||||
|
@ -452,16 +449,10 @@ class WebApi(JSONComponent):
|
||||||
:returns: the methods the daemon supports
|
:returns: the methods the daemon supports
|
||||||
:rtype: list
|
:rtype: list
|
||||||
"""
|
"""
|
||||||
d = Deferred()
|
host = self._get_host(host_id)
|
||||||
|
|
||||||
def on_connected(methods):
|
|
||||||
d.callback(methods)
|
|
||||||
host = self.get_host(host_id)
|
|
||||||
if host:
|
if host:
|
||||||
self._json.connect(*host[1:]).addCallback(on_connected)
|
return self._connect_daemon(*host[1:])
|
||||||
else:
|
|
||||||
return defer.fail(Exception("Bad host id"))
|
return defer.fail(Exception("Bad host id"))
|
||||||
return d
|
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def connected(self):
|
def connected(self):
|
||||||
|
@ -478,8 +469,12 @@ class WebApi(JSONComponent):
|
||||||
"""
|
"""
|
||||||
Disconnect the web interface from the connected daemon.
|
Disconnect the web interface from the connected daemon.
|
||||||
"""
|
"""
|
||||||
client.disconnect()
|
d = client.disconnect()
|
||||||
return True
|
|
||||||
|
def on_disconnect(reason):
|
||||||
|
return str(reason)
|
||||||
|
d.addCallback(on_disconnect)
|
||||||
|
return d
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def update_ui(self, keys, filter_dict):
|
def update_ui(self, keys, filter_dict):
|
||||||
|
@ -677,47 +672,7 @@ class WebApi(JSONComponent):
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def get_magnet_info(self, uri):
|
def get_magnet_info(self, uri):
|
||||||
"""
|
return common.get_magnet_info(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
|
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def add_torrents(self, torrents):
|
def add_torrents(self, torrents):
|
||||||
|
@ -737,7 +692,7 @@ class WebApi(JSONComponent):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for torrent in torrents:
|
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`",
|
log.info("Adding torrent from magnet uri `%s` with options `%r`",
|
||||||
torrent["path"], torrent["options"])
|
torrent["path"], torrent["options"])
|
||||||
client.core.add_torrent_magnet(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
|
return host_id, host, port, status, info
|
||||||
|
|
||||||
try:
|
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:
|
except TypeError:
|
||||||
host = None
|
host = None
|
||||||
port = None
|
port = None
|
||||||
|
@ -821,7 +776,7 @@ class WebApi(JSONComponent):
|
||||||
:type host_id: string
|
:type host_id: string
|
||||||
"""
|
"""
|
||||||
main_deferred = Deferred()
|
main_deferred = Deferred()
|
||||||
host = self.get_host(host_id)
|
host = self._get_host(host_id)
|
||||||
if not host:
|
if not host:
|
||||||
main_deferred.callback((False, _("Daemon doesn't exist")))
|
main_deferred.callback((False, _("Daemon doesn't exist")))
|
||||||
return main_deferred
|
return main_deferred
|
||||||
|
@ -864,7 +819,7 @@ class WebApi(JSONComponent):
|
||||||
# Check to see if there is already an entry for this host and return
|
# Check to see if there is already an entry for this host and return
|
||||||
# if thats the case
|
# if thats the case
|
||||||
for entry in self.host_list["hosts"]:
|
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")
|
return (False, "Host already in the list")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -877,7 +832,7 @@ class WebApi(JSONComponent):
|
||||||
self.host_list["hosts"].append([connection_id, host, port, username,
|
self.host_list["hosts"].append([connection_id, host, port, username,
|
||||||
password])
|
password])
|
||||||
self.host_list.save()
|
self.host_list.save()
|
||||||
return (True,)
|
return True, connection_id
|
||||||
|
|
||||||
@export
|
@export
|
||||||
def remove_host(self, connection_id):
|
def remove_host(self, connection_id):
|
||||||
|
@ -887,7 +842,7 @@ class WebApi(JSONComponent):
|
||||||
:param host_id: the hash id of the host
|
:param host_id: the hash id of the host
|
||||||
:type host_id: string
|
:type host_id: string
|
||||||
"""
|
"""
|
||||||
host = self.get_host(connection_id)
|
host = self._get_host(connection_id)
|
||||||
if host is None:
|
if host is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -919,6 +874,9 @@ class WebApi(JSONComponent):
|
||||||
"""
|
"""
|
||||||
web_config = component.get("DelugeWeb").config
|
web_config = component.get("DelugeWeb").config
|
||||||
for key in config.keys():
|
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):
|
if isinstance(config[key], basestring):
|
||||||
config[key] = config[key].encode("utf8")
|
config[key] = config[key].encode("utf8")
|
||||||
web_config[key] = config[key]
|
web_config[key] = config[key]
|
||||||
|
|
|
@ -16,7 +16,7 @@ import tempfile
|
||||||
|
|
||||||
from OpenSSL.crypto import FILETYPE_PEM
|
from OpenSSL.crypto import FILETYPE_PEM
|
||||||
from twisted.application import internet, service
|
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.internet.ssl import SSL, Certificate, CertificateOptions, KeyPair
|
||||||
from twisted.web import http, resource, server, static
|
from twisted.web import http, resource, server, static
|
||||||
|
|
||||||
|
@ -533,7 +533,6 @@ class DelugeWeb(component.Component):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(DelugeWeb, self).__init__("DelugeWeb")
|
super(DelugeWeb, self).__init__("DelugeWeb")
|
||||||
self.config = configmanager.ConfigManager("web.conf", CONFIG_DEFAULTS)
|
self.config = configmanager.ConfigManager("web.conf", CONFIG_DEFAULTS)
|
||||||
|
|
||||||
self.socket = None
|
self.socket = None
|
||||||
self.top_level = TopLevel()
|
self.top_level = TopLevel()
|
||||||
self.site = server.Site(self.top_level)
|
self.site = server.Site(self.top_level)
|
||||||
|
@ -544,7 +543,7 @@ class DelugeWeb(component.Component):
|
||||||
self.cert = self.config["cert"]
|
self.cert = self.config["cert"]
|
||||||
self.base = self.config["base"]
|
self.base = self.config["base"]
|
||||||
self.web_api = WebApi()
|
self.web_api = WebApi()
|
||||||
self.auth = Auth()
|
self.auth = Auth(self.config)
|
||||||
|
|
||||||
# Initalize the plugins
|
# Initalize the plugins
|
||||||
self.plugins = PluginManager()
|
self.plugins = PluginManager()
|
||||||
|
@ -568,16 +567,14 @@ class DelugeWeb(component.Component):
|
||||||
return 1
|
return 1
|
||||||
SetConsoleCtrlHandler(win_handler)
|
SetConsoleCtrlHandler(win_handler)
|
||||||
|
|
||||||
def start(self, start_reactor=True):
|
def start(self):
|
||||||
log.info("%s %s.", _("Starting server in PID"), os.getpid())
|
log.info("%s %s.", _("Starting server in PID"), os.getpid())
|
||||||
if self.https:
|
if self.https:
|
||||||
self.start_ssl()
|
self.start_ssl()
|
||||||
else:
|
else:
|
||||||
self.start_normal()
|
self.start_normal()
|
||||||
|
|
||||||
component.get("JSON").enable()
|
component.get("Web").enable()
|
||||||
|
|
||||||
if start_reactor:
|
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
|
||||||
def start_normal(self):
|
def start_normal(self):
|
||||||
|
@ -600,7 +597,7 @@ class DelugeWeb(component.Component):
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
log.info("Shutting down webserver")
|
log.info("Shutting down webserver")
|
||||||
component.get("JSON").disable()
|
component.get("Web").disable()
|
||||||
|
|
||||||
self.plugins.disable_plugins()
|
self.plugins.disable_plugins()
|
||||||
log.debug("Saving configuration file")
|
log.debug("Saving configuration file")
|
||||||
|
@ -616,10 +613,8 @@ class DelugeWeb(component.Component):
|
||||||
|
|
||||||
def shutdown(self, *args):
|
def shutdown(self, *args):
|
||||||
self.stop()
|
self.stop()
|
||||||
try:
|
|
||||||
reactor.stop()
|
reactor.stop()
|
||||||
except error.ReactorNotRunning:
|
|
||||||
log.debug("Reactor not running")
|
|
||||||
|
|
||||||
if __name__ == "__builtin__":
|
if __name__ == "__builtin__":
|
||||||
deluge_web = DelugeWeb()
|
deluge_web = DelugeWeb()
|
||||||
|
|
|
@ -16,7 +16,7 @@ frameworks = CoreFoundation, Foundation, AppKit
|
||||||
|
|
||||||
[isort]
|
[isort]
|
||||||
known_standard_library=unicodedata
|
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
|
known_first_party=msgfmt
|
||||||
order_by_type=true
|
order_by_type=true
|
||||||
line_length=120
|
line_length=120
|
||||||
|
|
1
tox.ini
1
tox.ini
|
@ -28,6 +28,7 @@ deps =
|
||||||
pyopenssl
|
pyopenssl
|
||||||
pyxdg
|
pyxdg
|
||||||
pytest
|
pytest
|
||||||
|
mock
|
||||||
whitelist_externals = py.test
|
whitelist_externals = py.test
|
||||||
commands = {envpython} setup.py test
|
commands = {envpython} setup.py test
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue