Skip to content

Commit

Permalink
Added reauthing logic to deluge client
Browse files Browse the repository at this point in the history
  • Loading branch information
moleculekayak committed Aug 2, 2024
1 parent 8ec3228 commit 56f8245
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 8 deletions.
28 changes: 21 additions & 7 deletions src/clients/deluge.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
import requests
from pathlib import Path

from ..errors import TorrentClientError
from ..errors import TorrentClientError, TorrentClientAuthenticationError
from .torrent_client import TorrentClient
from requests.exceptions import RequestException
from requests.structures import CaseInsensitiveDict


class Deluge(TorrentClient):
ERROR_CODES = {
"NO_AUTH": 1,
}

def __init__(self, rpc_url):
super().__init__()
self._rpc_url = rpc_url
Expand Down Expand Up @@ -37,7 +41,7 @@ def get_torrent_info(self, infohash):
{"hash": infohash},
]

response = self.__request("web.update_ui", params)
response = self.__wrap_request("web.update_ui", params)
if "torrents" in response:
torrent = response["torrents"].get(infohash)

Expand Down Expand Up @@ -75,7 +79,7 @@ def inject_torrent(self, source_torrent_infohash, new_torrent_filepath, save_pat
},
]

new_torrent_infohash = self.__request("core.add_torrent_file", params)
new_torrent_infohash = self.__wrap_request("core.add_torrent_file", params)
newtorrent_label = self.__determine_label(source_torrent_info)
self.__set_label(new_torrent_infohash, newtorrent_label)

Expand All @@ -86,14 +90,15 @@ def __authenticate(self):
if not password:
raise Exception("You need to define a password in the Deluge RPC URL. (e.g. http://:<PASSWORD>@localhost:8112)")

# This method specifically cannot use __wrap_request because an auth error would create an infinite loop
auth_response = self.__request("auth.login", [password])
if not auth_response:
raise TorrentClientError("Reached Deluge RPC endpoint but failed to authenticate")

return self.__request("web.connected")

def __is_label_plugin_enabled(self):
response = self.__request("core.get_enabled_plugins")
response = self.__wrap_request("core.get_enabled_plugins")

return "Label" in response

Expand All @@ -109,11 +114,18 @@ def __set_label(self, infohash, label):
if not self._label_plugin_enabled:
return

current_labels = self.__request("label.get_labels")
current_labels = self.__wrap_request("label.get_labels")
if label not in current_labels:
self.__request("label.add", [label])
self.__wrap_request("label.add", [label])

return self.__wrap_request("label.set_torrent", [infohash, label])

return self.__request("label.set_torrent", [infohash, label])
def __wrap_request(self, method, params=[]):
try:
return self.__request(method, params)
except TorrentClientAuthenticationError:
self.__authenticate()
return self.__request(method, params)

def __request(self, method, params=[]):
href, _, _ = self._extract_credentials_from_url(self._rpc_url)
Expand Down Expand Up @@ -148,6 +160,8 @@ def __request(self, method, params=[]):
self.__handle_response_headers(response.headers)

if "error" in json_response and json_response["error"]:
if json_response["error"]["code"] == self.ERROR_CODES["NO_AUTH"]:
raise TorrentClientAuthenticationError("Failed to authenticate with Deluge")
raise TorrentClientError(f"Deluge method {method} returned an error: {json_response['error']}")

return json_response["result"]
Expand Down
4 changes: 4 additions & 0 deletions src/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,9 @@ class TorrentClientError(Exception):
pass


class TorrentClientAuthenticationError(Exception):
pass


class TorrentInjectionError(Exception):
pass
26 changes: 25 additions & 1 deletion tests/clients/test_deluge.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
torrent_info_matcher,
)

from src.errors import TorrentClientError
from src.errors import TorrentClientError, TorrentClientAuthenticationError
from src.clients.deluge import Deluge


Expand Down Expand Up @@ -68,6 +68,15 @@ def test_raises_exception_on_failed_auth(self, api_url, deluge_client):

assert "Reached Deluge RPC endpoint but failed to authenticate" in str(excinfo.value)

def test_raises_exception_on_errored_auth(self, api_url, deluge_client):
with requests_mock.Mocker() as m:
m.post(api_url, additional_matcher=auth_matcher, json={"error": {"code": 1}})

with pytest.raises(TorrentClientAuthenticationError) as excinfo:
deluge_client.setup()

assert "Failed to authenticate with Deluge" in str(excinfo.value)

def test_sets_label_plugin_enabled_when_true(self, api_url, deluge_client):
assert not deluge_client._label_plugin_enabled

Expand Down Expand Up @@ -188,6 +197,21 @@ def test_returns_completed_if_seeding(self, api_url, deluge_client, torrent_info

assert response["complete"]

def test_attempts_reauth_if_deluge_cookie_expired(self, api_url, deluge_client, torrent_info_response):
with requests_mock.Mocker() as m:
m.post(api_url, additional_matcher=torrent_info_matcher, json={"error": {"code": 1}})
m.post(api_url, additional_matcher=auth_matcher, json={"result": True}, headers={"Set-Cookie": "supersecret"})
m.post(api_url, additional_matcher=connected_matcher, json={"result": True})

deluge_client._deluge_cookie = None
with pytest.raises(TorrentClientAuthenticationError):
deluge_client.get_torrent_info("foo")

assert deluge_client._deluge_cookie is not None
assert m.request_history[-3].json()["method"] == "auth.login"
assert m.request_history[-2].json()["method"] == "web.connected"
assert m.request_history[-1].json()["method"] == "web.update_ui"


class TestInjectTorrent(SetupTeardown):
def test_injects_torrent(self, api_url, deluge_client, torrent_info_response):
Expand Down

0 comments on commit 56f8245

Please sign in to comment.