Skip to content

Commit e89fc01

Browse files
committed
- Wait for commands to complete to avoid overloading the command queue
- Add a minimum donwload queue size to avoid overloading the download queue - fix to only scan individual movie instead of all movies
1 parent b60a893 commit e89fc01

File tree

5 files changed

+97
-32
lines changed

5 files changed

+97
-32
lines changed

api.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import datetime
1010
from typing import List, Dict, Any, Optional, Union
1111
from utils.logger import logger, debug_log
12-
from config import API_KEY, API_URL, API_TIMEOUT, MONITORED_ONLY, SKIP_FUTURE_RELEASES
12+
from config import API_KEY, API_URL, API_TIMEOUT, MONITORED_ONLY, SKIP_FUTURE_RELEASES, COMMAND_WAIT_DELAY, COMMAND_WAIT_ATTEMPTS
1313

1414
# Create a session for reuse
1515
session = requests.Session()
@@ -40,6 +40,44 @@ def radarr_request(endpoint: str, method: str = "GET", data: Dict = None) -> Opt
4040
logger.error(f"API request error: {e}")
4141
return None
4242

43+
def wait_for_command(command_id: int):
44+
logger.debug(f"Waiting for command {command_id} to complete...")
45+
attempts = 0
46+
while True:
47+
try:
48+
time.sleep(COMMAND_WAIT_DELAY)
49+
response = radarr_request(f"command/{command_id}")
50+
logger.debug(f"Command {command_id} Status: {response['status']}")
51+
except Exception as error:
52+
logger.error(f"Error fetching command status on attempt {attempts + 1}: {error}")
53+
return False
54+
55+
attempts += 1
56+
57+
if response['status'].lower() in ['complete', 'completed'] or attempts >= COMMAND_WAIT_ATTEMPTS:
58+
break
59+
60+
if response['status'].lower() not in ['complete', 'completed']:
61+
logger.warning(f"Command {command_id} did not complete within the allowed attempts.")
62+
return False
63+
64+
time.sleep(0.5)
65+
66+
return response['status'].lower() in ['complete', 'completed']
67+
68+
def get_download_queue_size() -> Optional[int]:
69+
"""
70+
GET /api/v3/queue
71+
Returns total number of items in the queue with the status 'downloading'.
72+
"""
73+
response = radarr_request("queue?status=downloading")
74+
total_records = response.get("totalRecords", 0)
75+
if not isinstance(total_records, int):
76+
total_records = 0
77+
logger.debug(f"Download Queue Size: {total_records}")
78+
79+
return total_records
80+
4381
def get_movies() -> List[Dict]:
4482
"""Get all movies from Radarr (full list)"""
4583
result = radarr_request("movie")
@@ -117,20 +155,23 @@ def refresh_movie(movie_id: int) -> Optional[Dict]:
117155
"name": "RefreshMovie",
118156
"movieIds": [movie_id]
119157
}
120-
return radarr_request("command", method="POST", data=data)
158+
response = radarr_request("command", method="POST", data=data)
159+
return wait_for_command(response['id'])
121160

122161
def movie_search(movie_id: int) -> Optional[Dict]:
123162
"""Search for a movie by ID"""
124163
data = {
125164
"name": "MoviesSearch",
126165
"movieIds": [movie_id]
127166
}
128-
return radarr_request("command", method="POST", data=data)
167+
response = radarr_request("command", method="POST", data=data)
168+
return wait_for_command(response['id'])
129169

130170
def rescan_movie(movie_id: int) -> Optional[Dict]:
131171
"""Rescan movie files"""
132172
data = {
133173
"name": "RescanMovie",
134-
"movieIds": [movie_id]
174+
"movieId": movie_id
135175
}
136-
return radarr_request("command", method="POST", data=data)
176+
response = radarr_request("command", method="POST", data=data)
177+
return wait_for_command(response['id'])

config.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@
4646
STATE_RESET_INTERVAL_HOURS = 168
4747
print(f"Warning: Invalid STATE_RESET_INTERVAL_HOURS value, using default: {STATE_RESET_INTERVAL_HOURS}")
4848

49+
# Delay in seconds between checking the status of a command (default 1 second)
50+
try:
51+
COMMAND_WAIT_DELAY = int(os.environ.get("COMMAND_WAIT_DELAY", "1"))
52+
except ValueError:
53+
COMMAND_WAIT_DELAY = 1
54+
print(f"Warning: Invalid COMMAND_WAIT_DELAY value, using default: {COMMAND_WAIT_DELAY}")
55+
56+
# Number of attempts to wait for a command to complete before giving up (default 600 attempts)
57+
try:
58+
COMMAND_WAIT_ATTEMPTS = int(os.environ.get("COMMAND_WAIT_ATTEMPTS", "600"))
59+
except ValueError:
60+
COMMAND_WAIT_ATTEMPTS = 600
61+
print(f"Warning: Invalid COMMAND_WAIT_ATTEMPTS value, using default: {COMMAND_WAIT_ATTEMPTS}")
62+
63+
# Minimum size of the download queue before starting a hunt (default -1)
64+
try:
65+
MINIMUM_DOWNLOAD_QUEUE_SIZE = int(os.environ.get("MINIMUM_DOWNLOAD_QUEUE_SIZE", "-1"))
66+
except ValueError:
67+
MINIMUM_DOWNLOAD_QUEUE_SIZE = -1
68+
print(f"Warning: Invalid MINIMUM_DOWNLOAD_QUEUE_SIZE value, using default: {MINIMUM_DOWNLOAD_QUEUE_SIZE}")
69+
4970
# Selection Settings
5071
RANDOM_SELECTION = os.environ.get("RANDOM_SELECTION", "true").lower() == "true"
5172
MONITORED_ONLY = os.environ.get("MONITORED_ONLY", "true").lower() == "true"
@@ -65,7 +86,9 @@ def log_configuration(logger):
6586
logger.info(f"Missing Content Configuration: HUNT_MISSING_MOVIES={HUNT_MISSING_MOVIES}")
6687
logger.info(f"Upgrade Configuration: HUNT_UPGRADE_MOVIES={HUNT_UPGRADE_MOVIES}")
6788
logger.info(f"State Reset Interval: {STATE_RESET_INTERVAL_HOURS} hours")
89+
logger.info(f"Minimum Download Queue Size: {MINIMUM_DOWNLOAD_QUEUE_SIZE}")
6890
logger.info(f"MONITORED_ONLY={MONITORED_ONLY}, RANDOM_SELECTION={RANDOM_SELECTION}")
6991
logger.info(f"SKIP_FUTURE_RELEASES={SKIP_FUTURE_RELEASES}")
7092
logger.info(f"HUNT_MODE={HUNT_MODE}, SLEEP_DURATION={SLEEP_DURATION}s")
93+
logger.info(f"COMMAND_WAIT_DELAY={COMMAND_WAIT_DELAY}, COMMAND_WAIT_ATTEMPTS={COMMAND_WAIT_ATTEMPTS}")
7194
logger.debug(f"API_KEY={API_KEY}")

main.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
import time
88
import sys
99
from utils.logger import logger
10-
from config import HUNT_MODE, SLEEP_DURATION, log_configuration
10+
from config import HUNT_MODE, SLEEP_DURATION, MINIMUM_DOWNLOAD_QUEUE_SIZE, log_configuration
1111
from missing import process_missing_movies
1212
from upgrade import process_cutoff_upgrades
1313
from state import check_state_reset, calculate_reset_time
14+
from api import get_download_queue_size
1415

1516
def main_loop() -> None:
1617
"""Main processing loop for Huntarr-Radarr"""
@@ -23,15 +24,22 @@ def main_loop() -> None:
2324
# Track if any processing was done in this cycle
2425
processing_done = False
2526

26-
# Process movies based on HUNT_MODE
27-
if HUNT_MODE in ["missing", "both"]:
28-
if process_missing_movies():
29-
processing_done = True
30-
31-
if HUNT_MODE in ["upgrade", "both"]:
32-
if process_cutoff_upgrades():
33-
processing_done = True
27+
# Check if we should ignore the download queue size or if we are below the minimum queue size
28+
download_queue_size = get_download_queue_size()
29+
if MINIMUM_DOWNLOAD_QUEUE_SIZE < 0 or (MINIMUM_DOWNLOAD_QUEUE_SIZE >= 0 and download_queue_size <= MINIMUM_DOWNLOAD_QUEUE_SIZE):
3430

31+
# Process movies based on HUNT_MODE
32+
if HUNT_MODE in ["missing", "both"]:
33+
if process_missing_movies():
34+
processing_done = True
35+
36+
if HUNT_MODE in ["upgrade", "both"]:
37+
if process_cutoff_upgrades():
38+
processing_done = True
39+
40+
else:
41+
logger.info(f"Download queue size ({download_queue_size}) is above the minimum threshold ({MINIMUM_DOWNLOAD_QUEUE_SIZE}). Skipped processing.")
42+
3543
# Calculate time until the next reset
3644
calculate_reset_time()
3745

missing.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,15 @@ def process_missing_movies() -> bool:
5858
# Refresh
5959
logger.info(" - Refreshing movie...")
6060
refresh_res = refresh_movie(movie_id)
61-
if not refresh_res or 'id' not in refresh_res:
61+
if not refresh_res:
6262
logger.warning(f"WARNING: Refresh command failed for {title}. Skipping.")
63-
time.sleep(10)
6463
continue
6564

66-
logger.info(f"Refresh command accepted (ID: {refresh_res.get('id')}). Waiting 5s...")
67-
time.sleep(5)
68-
6965
# Search
7066
logger.info(f" - Searching for \"{title}\"...")
7167
search_res = movie_search(movie_id)
72-
if search_res and 'id' in search_res:
73-
logger.info(f"Search command accepted (ID: {search_res.get('id')}).")
68+
if search_res:
69+
logger.info(f"Search command completed successfully.")
7470
processing_done = True
7571
else:
7672
logger.warning("WARNING: Movie search failed.")
@@ -79,8 +75,8 @@ def process_missing_movies() -> bool:
7975
# Rescan
8076
logger.info(" - Rescanning movie folder...")
8177
rescan_res = rescan_movie(movie_id)
82-
if rescan_res and 'id' in rescan_res:
83-
logger.info(f"Rescan command accepted (ID: {rescan_res.get('id')}).")
78+
if rescan_res:
79+
logger.info(f"Rescan command completed successfully.")
8480
else:
8581
logger.warning("WARNING: Rescan command not available or failed.")
8682

upgrade.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,26 +57,24 @@ def process_cutoff_upgrades() -> bool:
5757
# Refresh
5858
logger.info(" - Refreshing movie information...")
5959
refresh_res = refresh_movie(movie_id)
60-
if not refresh_res or 'id' not in refresh_res:
60+
if not refresh_res:
6161
logger.warning("WARNING: Refresh command failed. Skipping this movie.")
62-
time.sleep(10)
6362
continue
6463

65-
logger.info(f"Refresh command accepted (ID: {refresh_res.get('id')}). Waiting 5s...")
66-
time.sleep(5)
64+
logger.info(f"Refresh command completed successfully.")
6765

6866
# Search
6967
logger.info(" - Searching for quality upgrade...")
7068
search_res = movie_search(movie_id)
71-
if search_res and 'id' in search_res:
72-
logger.info(f"Search command accepted (ID: {search_res.get('id')}).")
69+
if search_res:
70+
logger.info(f"Search command completed successfully.")
7371
processing_done = True
7472

7573
# Rescan
7674
logger.info(" - Rescanning movie folder...")
7775
rescan_res = rescan_movie(movie_id)
78-
if rescan_res and 'id' in rescan_res:
79-
logger.info(f"Rescan command accepted (ID: {rescan_res.get('id')}).")
76+
if rescan_res:
77+
logger.info(f"Rescan command completed successfully.")
8078
else:
8179
logger.warning("WARNING: Rescan command not available or failed.")
8280

@@ -86,7 +84,6 @@ def process_cutoff_upgrades() -> bool:
8684
logger.info(f"Processed {movies_processed}/{HUNT_UPGRADE_MOVIES} upgrade movies this cycle.")
8785
else:
8886
logger.warning(f"WARNING: Search command failed for movie ID {movie_id}.")
89-
time.sleep(10)
9087

9188
logger.info(f"Completed processing {movies_processed} upgrade movies for this cycle.")
9289
truncate_processed_list(PROCESSED_UPGRADE_FILE)

0 commit comments

Comments
 (0)