mirror of
https://github.com/ManiMatter/decluttarr.git
synced 2026-04-20 15:55:40 +02:00
Added tip for wrong/missing download_client names
This commit is contained in:
@@ -17,7 +17,9 @@ class DownloadClients:
|
||||
download_clients = config.get("download_clients", {})
|
||||
if isinstance(download_clients, dict):
|
||||
self.qbittorrent = QbitClients(config, settings)
|
||||
if not self.qbittorrent: # Unsets settings in general section needed for qbit (if no qbit is defined)
|
||||
if (
|
||||
not self.qbittorrent
|
||||
): # Unsets settings in general section needed for qbit (if no qbit is defined)
|
||||
for key in [
|
||||
"private_tracker_handling",
|
||||
"public_tracker_handling",
|
||||
@@ -55,19 +57,65 @@ class DownloadClients:
|
||||
raise ValueError(error)
|
||||
|
||||
if name.lower() in seen:
|
||||
error = f"Download client names must be unique. Duplicate name found: '{name}'\nMake sure that the name corresponds with the name set in your *arr app for that download client."
|
||||
error = (
|
||||
f"Duplicate download client name detected: '{name}'.\n"
|
||||
"Download client names must be unique across all *arr instances.\n"
|
||||
"Ensure that the name configured for each download client in your *arr apps exactly matches the one used in your decluttarr configuration.\n"
|
||||
"Even if the names are unique within each individual *arr instance, they must also be globally unique across all configured instances.\n"
|
||||
"To fix this, assign a unique name to each download client in your *arr apps, and use that exact name in the decluttarr config.\n"
|
||||
"Example:\n"
|
||||
"If you use two qBittorrent clients—one for Radarr and one for Sonarr—name them distinctly in their respective *arr apps (e.g., 'qbittorrent_radarr' and 'qbittorrent_sonarr'), "
|
||||
"and refer to them with those names in the decluttarr config."
|
||||
)
|
||||
raise ValueError(error)
|
||||
seen.add(name.lower())
|
||||
|
||||
def get_download_client_by_name(self, name: str):
|
||||
"""Retrieve the download client and its type by its name."""
|
||||
name_lower = name.lower()
|
||||
for download_client_type in DOWNLOAD_CLIENT_TYPES:
|
||||
download_clients = getattr(self, download_client_type, [])
|
||||
|
||||
# Check each client in the list
|
||||
def get_download_client_by_name(
|
||||
self, name: str, download_client_type: str | None = None
|
||||
):
|
||||
"""
|
||||
Retrieve the download client and download client type by its name.
|
||||
If download_client_type is provided, search only in that type.
|
||||
"""
|
||||
name_lower = name.lower()
|
||||
types_to_search = (
|
||||
[download_client_type] if download_client_type else DOWNLOAD_CLIENT_TYPES
|
||||
)
|
||||
|
||||
for client_type in types_to_search:
|
||||
download_clients = getattr(self, client_type, [])
|
||||
|
||||
for download_client in download_clients:
|
||||
if download_client.name.lower() == name_lower:
|
||||
return download_client, download_client_type
|
||||
return download_client, client_type
|
||||
|
||||
return None, None
|
||||
|
||||
@staticmethod
|
||||
def get_download_client_type_from_implementation(
|
||||
arr_download_client_implementation: str,
|
||||
) -> str | None:
|
||||
"""
|
||||
Maps *arr download client implementation names to decluttarr download client type
|
||||
"""
|
||||
mapping = {
|
||||
"QBittorrent": "qbittorrent",
|
||||
# Only qbit configured for now
|
||||
}
|
||||
download_client_type = mapping.get(arr_download_client_implementation)
|
||||
return download_client_type
|
||||
|
||||
|
||||
def list_download_clients(self) -> dict[str, list[str]]:
|
||||
"""
|
||||
Return a dict mapping download_client_type to list of client names
|
||||
for all configured download clients.
|
||||
"""
|
||||
result: dict[str, list[str]] = {}
|
||||
|
||||
for client_type in DOWNLOAD_CLIENT_TYPES:
|
||||
download_clients = getattr(self, client_type, [])
|
||||
result[client_type] = [client.name for client in download_clients]
|
||||
|
||||
return result
|
||||
@@ -9,7 +9,7 @@ from src.settings._constants import (
|
||||
FullQueueParameter,
|
||||
MinVersions,
|
||||
)
|
||||
from src.utils.common import make_request, wait_and_exit
|
||||
from src.utils.common import make_request, wait_and_exit, extract_json_from_response
|
||||
from src.utils.log_setup import logger
|
||||
|
||||
|
||||
@@ -66,17 +66,17 @@ class Instances:
|
||||
def config_as_yaml(self, *, hide_internal_attr=True):
|
||||
"""Log all configured Arr instances while masking sensitive attributes."""
|
||||
internal_attributes = {
|
||||
"settings",
|
||||
"api_url",
|
||||
"min_version",
|
||||
"arr_type",
|
||||
"full_queue_parameter",
|
||||
"monitored_item",
|
||||
"detail_item_key",
|
||||
"detail_item_id_key",
|
||||
"detail_item_ids_key",
|
||||
"detail_item_search_command",
|
||||
}
|
||||
"settings",
|
||||
"api_url",
|
||||
"min_version",
|
||||
"arr_type",
|
||||
"full_queue_parameter",
|
||||
"monitored_item",
|
||||
"detail_item_key",
|
||||
"detail_item_id_key",
|
||||
"detail_item_ids_key",
|
||||
"detail_item_search_command",
|
||||
}
|
||||
|
||||
outputs = []
|
||||
for arr_type in ["sonarr", "radarr", "readarr", "lidarr", "whisparr"]:
|
||||
@@ -207,17 +207,25 @@ class ArrInstance:
|
||||
async def _check_reachability(self):
|
||||
"""Check if ARR instance is reachable."""
|
||||
try:
|
||||
logger.debug("_instances.py/_check_reachability: Checking if arr instance is reachable")
|
||||
logger.debug(
|
||||
"_instances.py/_check_reachability: Checking if arr instance is reachable"
|
||||
)
|
||||
endpoint = self.api_url + "/system/status"
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
response = await make_request(
|
||||
"get", endpoint, self.settings, headers=headers, log_error=False,
|
||||
"get",
|
||||
endpoint,
|
||||
self.settings,
|
||||
headers=headers,
|
||||
log_error=False,
|
||||
)
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
if isinstance(e, requests.exceptions.HTTPError):
|
||||
response = getattr(e, "response", None)
|
||||
if response is not None and response.status_code == 401: # noqa: PLR2004
|
||||
if (
|
||||
response is not None and response.status_code == 401
|
||||
): # noqa: PLR2004
|
||||
tip = "💡 Tip: Have you configured the API_KEY correctly?"
|
||||
else:
|
||||
tip = f"💡 Tip: HTTP error occurred. Status: {getattr(response, 'status_code', 'unknown')}"
|
||||
@@ -241,32 +249,76 @@ class ArrInstance:
|
||||
# Display result
|
||||
logger.info(f"OK | {self.name} ({self.base_url})")
|
||||
logger.debug(f"Current version of {self.name}: {self.version}")
|
||||
await self._check_matching_decluttarr_download_clients()
|
||||
|
||||
except Exception as e: # noqa: BLE001
|
||||
if not isinstance(e, ArrError):
|
||||
logger.error(f"Unhandled error: {e}", exc_info=True)
|
||||
wait_and_exit()
|
||||
|
||||
async def get_download_client_implementation(self, download_client_name):
|
||||
"""Fetch download client information and return the implementation value."""
|
||||
logger.debug("_instances.py/get_download_client_implementation: Checking type of download client type by download client name")
|
||||
async def fetch_arr_download_clients(self) -> list[dict[str, object]]:
|
||||
"""Fetch the list of download clients from the *arr API."""
|
||||
logger.debug(
|
||||
"_instances.py/fetch_download_clients: Fetching download client list from arr API"
|
||||
)
|
||||
endpoint = self.api_url + "/downloadclient"
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
|
||||
# Fetch the download client list from the API
|
||||
response = await make_request("get", endpoint, self.settings, headers=headers)
|
||||
return extract_json_from_response(response)
|
||||
|
||||
# Check if the response is a list
|
||||
download_clients = response.json()
|
||||
async def _check_matching_decluttarr_download_clients(self):
|
||||
"""Checks if there are any matching decluttarr settings for the download clients present in the arr"""
|
||||
arr_download_clients = await self.fetch_arr_download_clients()
|
||||
download_clients = self.settings.download_clients
|
||||
for arr_download_client in arr_download_clients:
|
||||
# Check if the download client in arr corresponds to one that decluttarr supports
|
||||
arr_download_client_name = arr_download_client.get("name")
|
||||
arr_implementation = arr_download_client.get("implementation")
|
||||
download_client_type = (
|
||||
download_clients.get_download_client_type_from_implementation(
|
||||
arr_implementation
|
||||
)
|
||||
)
|
||||
# If it is supported, check if there are any configured in decluttarr that match on the name
|
||||
if download_client_type:
|
||||
download_client, _ = download_clients.get_download_client_by_name(
|
||||
name=arr_download_client_name,
|
||||
download_client_type=download_client_type,
|
||||
)
|
||||
if not download_client:
|
||||
download_client_list = download_clients.list_download_clients().get(
|
||||
download_client_type
|
||||
)
|
||||
if not download_client_list:
|
||||
tip = (
|
||||
f"💡 Tip: In your {self.name} settings, you have a {download_client_type} download client configured named '{arr_download_client_name}'.\n"
|
||||
"However, in your decluttarr settings under 'download_clients', there is nothing configured.\n"
|
||||
"Adding a matching entry to your decluttarr settings will enable you to fully leverage the features and benefits that decluttarr brings."
|
||||
)
|
||||
else:
|
||||
tip = (
|
||||
f"💡 Tip: In your {self.name} settings, you have a {download_client_type} download client configured named '{arr_download_client_name}'.\n"
|
||||
"However, in your decluttarr settings under 'download_clients', there is no entry that matches this name.\n"
|
||||
"Adding a matching entry to your decluttarr settings will enable you to fully leverage the features and benefits that decluttarr brings.\n"
|
||||
f"Currently, your configured download clients are: {download_client_list}"
|
||||
)
|
||||
logger.info(tip)
|
||||
return
|
||||
|
||||
# Find the client where the name matches client_name
|
||||
for client in download_clients:
|
||||
if client.get("name") == download_client_name:
|
||||
# Return the implementation value if found
|
||||
return client.get("implementation", None)
|
||||
return None
|
||||
# async def get_download_client_implementation(self, download_client_name: str) -> str | None:
|
||||
# """Return the 'implementation' field of a specific download client by name."""
|
||||
# logger.debug("_instances.py/get_download_client_implementation: Looking up implementation for download client '%s'", download_client_name)
|
||||
|
||||
async def remove_queue_item(self, queue_id, *, blocklist=False):
|
||||
# arr_download_clients = await self.fetch_arr_download_clients()
|
||||
|
||||
# for arr_download_client in arr_download_clients:
|
||||
# if arr_download_client.get("name") == download_client_name:
|
||||
# return arr_download_client.get("implementation")
|
||||
|
||||
# return None
|
||||
|
||||
async def remove_queue_item(self, queue_id, *, blocklist=False):
|
||||
"""
|
||||
Remove a specific queue item from the queue by its queue id.
|
||||
|
||||
@@ -280,14 +332,20 @@ class ArrInstance:
|
||||
bool: Returns True if the removal was successful, False otherwise.
|
||||
|
||||
"""
|
||||
logger.debug(f"_instances.py/remove_queue_item: Removing queue item, blocklist: {blocklist}")
|
||||
logger.debug(
|
||||
f"_instances.py/remove_queue_item: Removing queue item, blocklist: {blocklist}"
|
||||
)
|
||||
endpoint = f"{self.api_url}/queue/{queue_id}"
|
||||
headers = {"X-Api-Key": self.api_key}
|
||||
json_payload = {"removeFromClient": True, "blocklist": blocklist}
|
||||
|
||||
# Send the request to remove the download from the queue
|
||||
response = await make_request(
|
||||
"delete", endpoint, self.settings, headers=headers, json=json_payload,
|
||||
"delete",
|
||||
endpoint,
|
||||
self.settings,
|
||||
headers=headers,
|
||||
json=json_payload,
|
||||
)
|
||||
|
||||
# If the response is successful, return True, else return False
|
||||
|
||||
Reference in New Issue
Block a user