From 911ad6b11ad441b90ea82c0b7db818580a2ca5a6 Mon Sep 17 00:00:00 2001
From: Admin9705 <9705@duck.com>
Date: Tue, 8 Apr 2025 20:40:47 -0400
Subject: [PATCH 1/6] update
---
web_server.py | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/web_server.py b/web_server.py
index 5a8e6908..0220d708 100644
--- a/web_server.py
+++ b/web_server.py
@@ -89,18 +89,20 @@ def update_settings():
if "huntarr" in data:
old_settings = settings_manager.get_setting("huntarr", None, {})
for key, value in data["huntarr"].items():
- old_value = old_settings.get(key, "Default") # Use "Default" instead of None for display
+ old_value = old_settings.get(key)
if old_value != value:
- changes_log.append(f"Changed {key} from {old_value} to {value}")
+ # Remove the "from Default" text - just log the new value
+ changes_log.append(f"Changed {key} to {value}")
settings_manager.update_setting("huntarr", key, value)
# Update UI settings
if "ui" in data:
old_settings = settings_manager.get_setting("ui", None, {})
for key, value in data["ui"].items():
- old_value = old_settings.get(key, "Default") # Use "Default" instead of None for display
+ old_value = old_settings.get(key)
if old_value != value:
- changes_log.append(f"Changed UI.{key} from {old_value} to {value}")
+ # Remove the "from Default" text - just log the new value
+ changes_log.append(f"Changed UI.{key} to {value}")
settings_manager.update_setting("ui", key, value)
# Write changes to log file
@@ -145,10 +147,11 @@ def update_theme():
if "dark_mode" in data and old_value != data["dark_mode"]:
settings_manager.update_setting("ui", "dark_mode", data["dark_mode"])
- # Log the theme change
+ # Log the theme change - simplified to remove "from X" text
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(LOG_FILE, 'a') as f:
- f.write(f"{timestamp} - huntarr-web - INFO - Changed theme from {'Dark' if old_value else 'Light'} to {'Dark' if data['dark_mode'] else 'Light'} Mode\n")
+ new_mode = 'Dark' if data['dark_mode'] else 'Light'
+ f.write(f"{timestamp} - huntarr-web - INFO - Changed theme to {new_mode} Mode\n")
return jsonify({"success": True})
except Exception as e:
From 6f56ca5baa5be6e2006fecc397c3291783bd4a83 Mon Sep 17 00:00:00 2001
From: Admin9705 <9705@duck.com>
Date: Tue, 8 Apr 2025 20:54:32 -0400
Subject: [PATCH 2/6] update
---
config.py | 30 ++++++++++++++++++++-----
missing.py | 15 +++++++++----
settings_manager.py | 9 ++++++++
static/js/main.js | 53 ++++++++++++++++++++++++++++++++++++++++++--
templates/index.html | 50 ++++++++++++++++++++++++++++++++++++++++-
upgrade.py | 37 ++++++++++++++++++++++---------
utils/logger.py | 44 +++++++++++++++++++++++++++---------
web_server.py | 18 ++++++++++++++-
8 files changed, 222 insertions(+), 34 deletions(-)
diff --git a/config.py b/config.py
index f2748576..a3abff55 100644
--- a/config.py
+++ b/config.py
@@ -15,7 +15,7 @@
API_KEY = os.environ.get("API_KEY", "your-api-key")
API_URL = os.environ.get("API_URL", "http://your-sonarr-address:8989")
-# API timeout in seconds
+# API timeout in seconds - load from environment first, will be overridden by settings if they exist
try:
API_TIMEOUT = int(os.environ.get("API_TIMEOUT", "60"))
except ValueError:
@@ -61,7 +61,7 @@
SKIP_FUTURE_EPISODES = os.environ.get("SKIP_FUTURE_EPISODES", "true").lower() == "true"
SKIP_SERIES_REFRESH = os.environ.get("SKIP_SERIES_REFRESH", "false").lower() == "true"
-# Delay in seconds between checking the status of a command (default 1 second)
+# Advanced settings - load from environment first, will be overridden by settings if they exist
try:
COMMAND_WAIT_DELAY = int(os.environ.get("COMMAND_WAIT_DELAY", "1"))
except ValueError:
@@ -82,21 +82,28 @@
MINIMUM_DOWNLOAD_QUEUE_SIZE = -1
print(f"Warning: Invalid MINIMUM_DOWNLOAD_QUEUE_SIZE value, using default: {MINIMUM_DOWNLOAD_QUEUE_SIZE}")
-# Hunt mode: "missing", "upgrade", or "both"
-HUNT_MODE = os.environ.get("HUNT_MODE", "both")
-
# Debug Settings
DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
+# Random selection for missing and upgrades
+RANDOM_MISSING = True # Will be overridden by settings
+RANDOM_UPGRADES = True # Will be overridden by settings
+
+# Hunt mode: "missing", "upgrade", or "both"
+HUNT_MODE = os.environ.get("HUNT_MODE", "both")
+
def refresh_settings():
"""Refresh configuration settings from the settings manager."""
global HUNT_MISSING_SHOWS, HUNT_UPGRADE_EPISODES, SLEEP_DURATION
global STATE_RESET_INTERVAL_HOURS, MONITORED_ONLY, RANDOM_SELECTION
global SKIP_FUTURE_EPISODES, SKIP_SERIES_REFRESH
+ global API_TIMEOUT, DEBUG_MODE, COMMAND_WAIT_DELAY, COMMAND_WAIT_ATTEMPTS
+ global MINIMUM_DOWNLOAD_QUEUE_SIZE, RANDOM_MISSING, RANDOM_UPGRADES
# Load settings directly from settings manager
settings = settings_manager.get_all_settings()
huntarr_settings = settings.get("huntarr", {})
+ advanced_settings = settings.get("advanced", {})
# Update global variables with fresh values
HUNT_MISSING_SHOWS = huntarr_settings.get("hunt_missing_shows", HUNT_MISSING_SHOWS)
@@ -108,10 +115,20 @@ def refresh_settings():
SKIP_FUTURE_EPISODES = huntarr_settings.get("skip_future_episodes", SKIP_FUTURE_EPISODES)
SKIP_SERIES_REFRESH = huntarr_settings.get("skip_series_refresh", SKIP_SERIES_REFRESH)
+ # Advanced settings
+ API_TIMEOUT = advanced_settings.get("api_timeout", API_TIMEOUT)
+ DEBUG_MODE = advanced_settings.get("debug_mode", DEBUG_MODE)
+ COMMAND_WAIT_DELAY = advanced_settings.get("command_wait_delay", COMMAND_WAIT_DELAY)
+ COMMAND_WAIT_ATTEMPTS = advanced_settings.get("command_wait_attempts", COMMAND_WAIT_ATTEMPTS)
+ MINIMUM_DOWNLOAD_QUEUE_SIZE = advanced_settings.get("minimum_download_queue_size", MINIMUM_DOWNLOAD_QUEUE_SIZE)
+ RANDOM_MISSING = advanced_settings.get("random_missing", RANDOM_MISSING)
+ RANDOM_UPGRADES = advanced_settings.get("random_upgrades", RANDOM_UPGRADES)
+
# Log the refresh for debugging
import logging
logger = logging.getLogger("huntarr-sonarr")
logger.debug(f"Settings refreshed: SLEEP_DURATION={SLEEP_DURATION}, HUNT_MISSING_SHOWS={HUNT_MISSING_SHOWS}")
+ logger.debug(f"Advanced settings refreshed: API_TIMEOUT={API_TIMEOUT}, DEBUG_MODE={DEBUG_MODE}")
def log_configuration(logger):
"""Log the current configuration settings"""
@@ -126,10 +143,11 @@ def log_configuration(logger):
logger.info(f"State Reset Interval: {STATE_RESET_INTERVAL_HOURS} hours")
logger.info(f"Minimum Download Queue Size: {MINIMUM_DOWNLOAD_QUEUE_SIZE}")
logger.info(f"MONITORED_ONLY={MONITORED_ONLY}, RANDOM_SELECTION={RANDOM_SELECTION}")
+ logger.info(f"RANDOM_MISSING={RANDOM_MISSING}, RANDOM_UPGRADES={RANDOM_UPGRADES}")
logger.info(f"HUNT_MODE={HUNT_MODE}, SLEEP_DURATION={SLEEP_DURATION}s")
logger.info(f"COMMAND_WAIT_DELAY={COMMAND_WAIT_DELAY}, COMMAND_WAIT_ATTEMPTS={COMMAND_WAIT_ATTEMPTS}")
logger.info(f"SKIP_FUTURE_EPISODES={SKIP_FUTURE_EPISODES}, SKIP_SERIES_REFRESH={SKIP_SERIES_REFRESH}")
- logger.info(f"ENABLE_WEB_UI={ENABLE_WEB_UI}")
+ logger.info(f"ENABLE_WEB_UI={ENABLE_WEB_UI}, DEBUG_MODE={DEBUG_MODE}")
logger.debug(f"API_KEY={API_KEY}")
# Initial refresh of settings
diff --git a/missing.py b/missing.py
index 86505352..67e45be3 100644
--- a/missing.py
+++ b/missing.py
@@ -12,7 +12,8 @@
from config import (
HUNT_MISSING_SHOWS,
MONITORED_ONLY,
- RANDOM_SELECTION,
+ RANDOM_SELECTION,
+ RANDOM_MISSING,
SKIP_FUTURE_EPISODES,
SKIP_SERIES_REFRESH
)
@@ -63,9 +64,14 @@ def process_missing_episodes() -> bool:
shows_processed = 0
processing_done = False
- # Optionally randomize show order
- if RANDOM_SELECTION:
+ # Optionally randomize show order - use RANDOM_MISSING setting
+ # First honor the legacy RANDOM_SELECTION, then the specific RANDOM_MISSING
+ should_randomize = RANDOM_SELECTION and RANDOM_MISSING
+ if should_randomize:
+ logger.info("Using random selection for missing shows")
random.shuffle(shows_with_missing)
+ else:
+ logger.info("Using sequential selection for missing shows")
# Get current date for future episode filtering
current_date = datetime.datetime.now().date()
@@ -164,4 +170,5 @@ def process_missing_episodes() -> bool:
# Truncate processed list if needed
truncate_processed_list(PROCESSED_MISSING_FILE)
- return processing_done
\ No newline at end of file
+ return processing_done
+ air_date_str = ep.get("
\ No newline at end of file
diff --git a/settings_manager.py b/settings_manager.py
index dfb9a435..3b8812e3 100644
--- a/settings_manager.py
+++ b/settings_manager.py
@@ -34,6 +34,15 @@
"random_selection": True,
"skip_future_episodes": True,
"skip_series_refresh": False
+ },
+ "advanced": {
+ "api_timeout": 60,
+ "debug_mode": False,
+ "command_wait_delay": 1,
+ "command_wait_attempts": 600,
+ "minimum_download_queue_size": -1,
+ "random_missing": True,
+ "random_upgrades": True
}
}
diff --git a/static/js/main.js b/static/js/main.js
index 47bd3e64..bd32719a 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.getElementById('themeToggle');
const themeLabel = document.getElementById('themeLabel');
- // Settings form elements
+ // Settings form elements - Basic settings
const huntMissingShowsInput = document.getElementById('hunt_missing_shows');
const huntUpgradeEpisodesInput = document.getElementById('hunt_upgrade_episodes');
const sleepDurationInput = document.getElementById('sleep_duration');
@@ -19,9 +19,18 @@ document.addEventListener('DOMContentLoaded', function() {
const stateResetIntervalInput = document.getElementById('state_reset_interval_hours');
const monitoredOnlyInput = document.getElementById('monitored_only');
const randomSelectionInput = document.getElementById('random_selection');
+ const randomMissingInput = document.getElementById('random_missing');
+ const randomUpgradesInput = document.getElementById('random_upgrades');
const skipFutureEpisodesInput = document.getElementById('skip_future_episodes');
const skipSeriesRefreshInput = document.getElementById('skip_series_refresh');
+ // Settings form elements - Advanced settings
+ const apiTimeoutInput = document.getElementById('api_timeout');
+ const debugModeInput = document.getElementById('debug_mode');
+ const commandWaitDelayInput = document.getElementById('command_wait_delay');
+ const commandWaitAttemptsInput = document.getElementById('command_wait_attempts');
+ const minimumDownloadQueueSizeInput = document.getElementById('minimum_download_queue_size');
+
// Button elements for saving and resetting settings
const saveSettingsButton = document.getElementById('saveSettings');
const resetSettingsButton = document.getElementById('resetSettings');
@@ -118,14 +127,27 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
+ // Sync random selection master with specific random options
+ randomSelectionInput.addEventListener('change', function() {
+ // If master is unchecked, disable the specific random options
+ if (!this.checked) {
+ randomMissingInput.disabled = true;
+ randomUpgradesInput.disabled = true;
+ } else {
+ randomMissingInput.disabled = false;
+ randomUpgradesInput.disabled = false;
+ }
+ });
+
// Load settings from API
function loadSettings() {
fetch('/api/settings')
.then(response => response.json())
.then(data => {
const huntarr = data.huntarr || {};
+ const advanced = data.advanced || {};
- // Fill form with current settings
+ // Fill form with current settings - Basic settings
huntMissingShowsInput.value = huntarr.hunt_missing_shows !== undefined ? huntarr.hunt_missing_shows : 1;
huntUpgradeEpisodesInput.value = huntarr.hunt_upgrade_episodes !== undefined ? huntarr.hunt_upgrade_episodes : 5;
sleepDurationInput.value = huntarr.sleep_duration || 900;
@@ -135,6 +157,24 @@ document.addEventListener('DOMContentLoaded', function() {
randomSelectionInput.checked = huntarr.random_selection !== false;
skipFutureEpisodesInput.checked = huntarr.skip_future_episodes !== false;
skipSeriesRefreshInput.checked = huntarr.skip_series_refresh === true;
+
+ // Fill form with current settings - Advanced settings
+ apiTimeoutInput.value = advanced.api_timeout || 60;
+ debugModeInput.checked = advanced.debug_mode === true;
+ commandWaitDelayInput.value = advanced.command_wait_delay || 1;
+ commandWaitAttemptsInput.value = advanced.command_wait_attempts || 600;
+ minimumDownloadQueueSizeInput.value = advanced.minimum_download_queue_size || -1;
+ randomMissingInput.checked = advanced.random_missing !== false;
+ randomUpgradesInput.checked = advanced.random_upgrades !== false;
+
+ // Sync random selection master with specific random options
+ if (!randomSelectionInput.checked) {
+ randomMissingInput.disabled = true;
+ randomUpgradesInput.disabled = true;
+ } else {
+ randomMissingInput.disabled = false;
+ randomUpgradesInput.disabled = false;
+ }
})
.catch(error => console.error('Error loading settings:', error));
}
@@ -151,6 +191,15 @@ document.addEventListener('DOMContentLoaded', function() {
random_selection: randomSelectionInput.checked,
skip_future_episodes: skipFutureEpisodesInput.checked,
skip_series_refresh: skipSeriesRefreshInput.checked
+ },
+ advanced: {
+ api_timeout: parseInt(apiTimeoutInput.value) || 60,
+ debug_mode: debugModeInput.checked,
+ command_wait_delay: parseInt(commandWaitDelayInput.value) || 1,
+ command_wait_attempts: parseInt(commandWaitAttemptsInput.value) || 600,
+ minimum_download_queue_size: parseInt(minimumDownloadQueueSizeInput.value) || -1,
+ random_missing: randomMissingInput.checked,
+ random_upgrades: randomUpgradesInput.checked
}
};
diff --git a/templates/index.html b/templates/index.html
index 60aaabbf..0bef8df0 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -97,7 +97,23 @@
Processing Options
- Select shows and episodes randomly instead of sequentially
+ Master switch for random selection (affects both missing and upgrades)
+
+
+
+
+
Select missing shows randomly instead of sequentially
+
+
+
+
+
Select upgrade episodes randomly instead of sequentially
@@ -116,6 +132,38 @@
Processing Options
Skip refreshing series metadata before processing (reduces disk activity)
+
+
+
Advanced Settings
+
+
+
+
Timeout in seconds for API requests to Sonarr (minimum 10 seconds)
+
+
+
+
+
Enable detailed debug logging (takes effect immediately)
+
+
+
+
+
Delay in seconds between checking for command status
+
+
+
+
+
Number of attempts to check for command completion before giving up
+
+
+
+
+
Minimum items in download queue before starting hunt. Set to -1 to disable check.
+
+
diff --git a/upgrade.py b/upgrade.py
index d702bdfb..9178674a 100644
--- a/upgrade.py
+++ b/upgrade.py
@@ -12,6 +12,7 @@
HUNT_UPGRADE_EPISODES,
MONITORED_ONLY,
RANDOM_SELECTION,
+ RANDOM_UPGRADES,
SKIP_FUTURE_EPISODES,
SKIP_SERIES_REFRESH
)
@@ -45,29 +46,47 @@ def process_cutoff_upgrades() -> bool:
# Get current date for future episode filtering
current_date = datetime.datetime.now().date()
- page = 1
+ # Determine if we should use random selection for upgrades
+ # First honor the legacy RANDOM_SELECTION, then the specific RANDOM_UPGRADES
+ should_use_random = RANDOM_SELECTION and RANDOM_UPGRADES
+
+ if should_use_random:
+ logger.info("Using random selection for quality upgrades")
+ else:
+ logger.info("Using sequential selection for quality upgrades")
+ page = 1
+
while True:
if episodes_processed >= HUNT_UPGRADE_EPISODES:
logger.info(f"Reached HUNT_UPGRADE_EPISODES={HUNT_UPGRADE_EPISODES} for this cycle.")
break
- # If random selection, pick a random page each iteration
- if RANDOM_SELECTION and total_pages > 1:
+ # If random selection is enabled, pick a random page each iteration
+ if should_use_random and total_pages > 1:
page = random.randint(1, total_pages)
+ # If sequential and we've reached the end, we're done
+ elif not should_use_random and page > total_pages:
+ break
logger.info(f"Retrieving cutoff-unmet episodes (page={page} of {total_pages})...")
cutoff_data = get_cutoff_unmet(page)
if not cutoff_data or "records" not in cutoff_data:
logger.error(f"ERROR: Unable to retrieve cutoffโunmet data from Sonarr on page {page}.")
- break
+
+ # In sequential mode, try the next page
+ if not should_use_random:
+ page += 1
+ continue
+ else:
+ break
episodes = cutoff_data["records"]
total_eps = len(episodes)
logger.info(f"Found {total_eps} episodes on page {page} that need quality upgrades.")
- # Randomize or sequential indices
+ # Randomize or sequential indices within the page
indices = list(range(total_eps))
- if RANDOM_SELECTION:
+ if should_use_random:
random.shuffle(indices)
for idx in indices:
@@ -149,11 +168,9 @@ def process_cutoff_upgrades() -> bool:
logger.warning(f"WARNING: Search command failed for episode ID {episode_id}.")
continue
- # Move to the next page if not random
- if not RANDOM_SELECTION:
+ # Move to the next page if using sequential mode
+ if not should_use_random:
page += 1
- if page > total_pages:
- break
else:
# In random mode, we just handle one random page this iteration,
# then either break or keep looping until we hit HUNT_UPGRADE_EPISODES.
diff --git a/utils/logger.py b/utils/logger.py
index 7a1150c5..b5abcea3 100644
--- a/utils/logger.py
+++ b/utils/logger.py
@@ -7,27 +7,48 @@
import sys
import os
import pathlib
-from config import DEBUG_MODE
+from config import DEBUG_MODE as CONFIG_DEBUG_MODE
# Create log directory
LOG_DIR = pathlib.Path("/tmp/huntarr-logs")
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOG_DIR / "huntarr.log"
-def setup_logger():
- """Configure and return the application logger"""
- logger = logging.getLogger("huntarr-sonarr")
+# Global logger instance
+logger = None
+
+def setup_logger(debug_mode=None):
+ """Configure and return the application logger
+
+ Args:
+ debug_mode (bool, optional): Override the DEBUG_MODE from config. Defaults to None.
+
+ Returns:
+ logging.Logger: The configured logger
+ """
+ global logger
- # Set the log level based on DEBUG_MODE
- logger.setLevel(logging.DEBUG if DEBUG_MODE else logging.INFO)
+ # Use the provided debug_mode if given, otherwise use the config value
+ use_debug_mode = debug_mode if debug_mode is not None else CONFIG_DEBUG_MODE
+
+ if logger is None:
+ # First-time setup
+ logger = logging.getLogger("huntarr-sonarr")
+ else:
+ # Reset handlers to avoid duplicates
+ for handler in logger.handlers[:]:
+ logger.removeHandler(handler)
+
+ # Set the log level based on use_debug_mode
+ logger.setLevel(logging.DEBUG if use_debug_mode else logging.INFO)
# Create console handler
console_handler = logging.StreamHandler(sys.stdout)
- console_handler.setLevel(logging.DEBUG if DEBUG_MODE else logging.INFO)
+ console_handler.setLevel(logging.DEBUG if use_debug_mode else logging.INFO)
# Create file handler for the web interface
file_handler = logging.FileHandler(LOG_FILE)
- file_handler.setLevel(logging.DEBUG if DEBUG_MODE else logging.INFO)
+ file_handler.setLevel(logging.DEBUG if use_debug_mode else logging.INFO)
# Set format
formatter = logging.Formatter(
@@ -41,14 +62,17 @@ def setup_logger():
logger.addHandler(console_handler)
logger.addHandler(file_handler)
+ if use_debug_mode:
+ logger.debug("Debug logging enabled")
+
return logger
-# Create the logger instance
+# Create the logger instance on module import
logger = setup_logger()
def debug_log(message: str, data: object = None) -> None:
"""Log debug messages with optional data."""
- if DEBUG_MODE:
+ if logger.level <= logging.DEBUG:
logger.debug(f"{message}")
if data is not None:
try:
diff --git a/web_server.py b/web_server.py
index 0220d708..8459a5ec 100644
--- a/web_server.py
+++ b/web_server.py
@@ -14,8 +14,9 @@
import logging
from config import ENABLE_WEB_UI
import settings_manager
+from utils.logger import setup_logger
-# Check if web UI is enabled
+# Check if web UI is disabled
if not ENABLE_WEB_UI:
print("Web UI is disabled. Exiting web server.")
exit(0)
@@ -105,6 +106,21 @@ def update_settings():
changes_log.append(f"Changed UI.{key} to {value}")
settings_manager.update_setting("ui", key, value)
+ # Update advanced settings
+ if "advanced" in data:
+ old_settings = settings_manager.get_setting("advanced", None, {})
+ for key, value in data["advanced"].items():
+ old_value = old_settings.get(key)
+ if old_value != value:
+ changes_log.append(f"Changed advanced.{key} to {value}")
+ settings_manager.update_setting("advanced", key, value)
+
+ # Special handling for debug_mode setting
+ if key == "debug_mode" and old_value != value:
+ # Reconfigure the logger with new debug mode setting
+ setup_logger(value)
+ changes_log.append(f"Reconfigured logger with DEBUG_MODE={value}")
+
# Write changes to log file
if changes_log:
with open(LOG_FILE, 'a') as f:
From c7150a1b5a2e42fb131d36cc87483c444c9e2f8c Mon Sep 17 00:00:00 2001
From: Admin9705 <9705@duck.com>
Date: Tue, 8 Apr 2025 21:08:59 -0400
Subject: [PATCH 3/6] i[date
---
api.py | 13 +++-
missing.py | 3 +-
utils/.github/workflows/docker-image.yml | 93 ------------------------
utils/logger.py | 9 ++-
4 files changed, 17 insertions(+), 101 deletions(-)
delete mode 100644 utils/.github/workflows/docker-image.yml
diff --git a/api.py b/api.py
index 1cf21b94..a7b06181 100644
--- a/api.py
+++ b/api.py
@@ -71,7 +71,7 @@ def get_series() -> List[Dict]:
debug_log("Raw series API response sample:", series_list[:2] if len(series_list) > 2 else series_list)
return series_list or []
-def refresh_series(series_id: int) -> Optional[Dict]:
+def refresh_series(series_id: int) -> bool:
"""
POST /api/v3/command
{
@@ -84,9 +84,11 @@ def refresh_series(series_id: int) -> Optional[Dict]:
"seriesId": series_id
}
response = sonarr_request("command", method="POST", data=data)
+ if not response or 'id' not in response:
+ return False
return wait_for_command(response['id'])
-def episode_search_episodes(episode_ids: List[int]) -> Optional[Dict]:
+def episode_search_episodes(episode_ids: List[int]) -> bool:
"""
POST /api/v3/command
{
@@ -99,14 +101,19 @@ def episode_search_episodes(episode_ids: List[int]) -> Optional[Dict]:
"episodeIds": episode_ids
}
response = sonarr_request("command", method="POST", data=data)
+ if not response or 'id' not in response:
+ return False
return wait_for_command(response['id'])
-def get_download_queue_size() -> Optional[int]:
+def get_download_queue_size() -> int:
"""
GET /api/v3/queue
Returns total number of items in the queue with the status 'downloading'.
"""
response = sonarr_request("queue?status=downloading")
+ if not response:
+ return 0
+
total_records = response.get("totalRecords", 0)
if not isinstance(total_records, int):
total_records = 0
diff --git a/missing.py b/missing.py
index 67e45be3..d515f7ee 100644
--- a/missing.py
+++ b/missing.py
@@ -170,5 +170,4 @@ def process_missing_episodes() -> bool:
# Truncate processed list if needed
truncate_processed_list(PROCESSED_MISSING_FILE)
- return processing_done
- air_date_str = ep.get("
\ No newline at end of file
+ return processing_done
\ No newline at end of file
diff --git a/utils/.github/workflows/docker-image.yml b/utils/.github/workflows/docker-image.yml
deleted file mode 100644
index ea7c086d..00000000
--- a/utils/.github/workflows/docker-image.yml
+++ /dev/null
@@ -1,93 +0,0 @@
-name: Docker Build and Push
-on:
- push:
- branches:
- - main
- - dev
- tags:
- - "*" # This will trigger on any tag push
- pull_request:
- branches:
- - main
-jobs:
- build-and-push:
- runs-on: ubuntu-latest
- steps:
- # 1) Check out your repository code with full depth
- - name: Checkout code
- uses: actions/checkout@v3
- with:
- fetch-depth: 0
-
- # 2) List files to verify huntarr.py is present
- - name: List files in directory
- run: ls -la
-
- # 3) Set up QEMU for multi-architecture builds
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v2
- with:
- platforms: arm64,amd64
-
- # 4) Set up Docker Buildx
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
-
- # 5) Log in to Docker Hub
- - name: Log in to Docker Hub
- if: github.event_name != 'pull_request'
- uses: docker/login-action@v2
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_PASSWORD }}
-
- # 6) Extract version from tag if it's a tag push
- - name: Extract version from tag
- if: startsWith(github.ref, 'refs/tags/')
- id: get_version
- run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
-
- # 7a) Build & Push if on 'main' branch
- - name: Build and Push (main)
- if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
- uses: docker/build-push-action@v3
- with:
- context: .
- push: true
- platforms: linux/amd64,linux/arm64
- tags: |
- huntarr/4sonarr:latest
- huntarr/4sonarr:${{ github.sha }}
-
- # 7b) Build & Push if on 'dev' branch
- - name: Build and Push (dev)
- if: github.ref == 'refs/heads/dev' && github.event_name != 'pull_request'
- uses: docker/build-push-action@v3
- with:
- context: .
- push: true
- platforms: linux/amd64,linux/arm64
- tags: |
- huntarr/4sonarr:dev
- huntarr/4sonarr:${{ github.sha }}
-
- # 7c) Build & Push if it's a tag/release
- - name: Build and Push (release)
- if: startsWith(github.ref, 'refs/tags/') && github.event_name != 'pull_request'
- uses: docker/build-push-action@v3
- with:
- context: .
- push: true
- platforms: linux/amd64,linux/arm64
- tags: |
- huntarr/4sonarr:${{ steps.get_version.outputs.VERSION }}
- huntarr/4sonarr:latest
-
- # 7d) Just build on pull requests
- - name: Build (PR)
- if: github.event_name == 'pull_request'
- uses: docker/build-push-action@v3
- with:
- context: .
- push: false
- platforms: linux/amd64,linux/arm64
\ No newline at end of file
diff --git a/utils/logger.py b/utils/logger.py
index b5abcea3..15b42357 100644
--- a/utils/logger.py
+++ b/utils/logger.py
@@ -7,7 +7,6 @@
import sys
import os
import pathlib
-from config import DEBUG_MODE as CONFIG_DEBUG_MODE
# Create log directory
LOG_DIR = pathlib.Path("/tmp/huntarr-logs")
@@ -28,8 +27,12 @@ def setup_logger(debug_mode=None):
"""
global logger
- # Use the provided debug_mode if given, otherwise use the config value
- use_debug_mode = debug_mode if debug_mode is not None else CONFIG_DEBUG_MODE
+ # Get DEBUG_MODE from config, but only if we haven't been given a value
+ if debug_mode is None:
+ from config import DEBUG_MODE as CONFIG_DEBUG_MODE
+ use_debug_mode = CONFIG_DEBUG_MODE
+ else:
+ use_debug_mode = debug_mode
if logger is None:
# First-time setup
From adba640628ec1ded0239405b0806a0046d1e35a7 Mon Sep 17 00:00:00 2001
From: Admin9705 <9705@duck.com>
Date: Tue, 8 Apr 2025 21:29:23 -0400
Subject: [PATCH 4/6] update
---
config.py | 22 +++++++++++++++++-----
missing.py | 11 +++++------
static/js/main.js | 31 +++++++++----------------------
templates/index.html | 6 +++---
upgrade.py | 18 ++++++++----------
5 files changed, 42 insertions(+), 46 deletions(-)
diff --git a/config.py b/config.py
index a3abff55..c1acec08 100644
--- a/config.py
+++ b/config.py
@@ -85,9 +85,10 @@
# Debug Settings
DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
-# Random selection for missing and upgrades
-RANDOM_MISSING = True # Will be overridden by settings
-RANDOM_UPGRADES = True # Will be overridden by settings
+# Random selection for missing and upgrades - default to RANDOM_SELECTION for backward compatibility
+# These can be overridden by environment variables or settings
+RANDOM_MISSING = os.environ.get("RANDOM_MISSING", str(RANDOM_SELECTION)).lower() == "true"
+RANDOM_UPGRADES = os.environ.get("RANDOM_UPGRADES", str(RANDOM_SELECTION)).lower() == "true"
# Hunt mode: "missing", "upgrade", or "both"
HUNT_MODE = os.environ.get("HUNT_MODE", "both")
@@ -121,14 +122,25 @@ def refresh_settings():
COMMAND_WAIT_DELAY = advanced_settings.get("command_wait_delay", COMMAND_WAIT_DELAY)
COMMAND_WAIT_ATTEMPTS = advanced_settings.get("command_wait_attempts", COMMAND_WAIT_ATTEMPTS)
MINIMUM_DOWNLOAD_QUEUE_SIZE = advanced_settings.get("minimum_download_queue_size", MINIMUM_DOWNLOAD_QUEUE_SIZE)
- RANDOM_MISSING = advanced_settings.get("random_missing", RANDOM_MISSING)
- RANDOM_UPGRADES = advanced_settings.get("random_upgrades", RANDOM_UPGRADES)
+
+ # Get the specific random settings - default to RANDOM_SELECTION for backward compatibility
+ # but only if not explicitly set in the advanced settings
+ if "random_missing" in advanced_settings:
+ RANDOM_MISSING = advanced_settings.get("random_missing")
+ else:
+ RANDOM_MISSING = RANDOM_SELECTION
+
+ if "random_upgrades" in advanced_settings:
+ RANDOM_UPGRADES = advanced_settings.get("random_upgrades")
+ else:
+ RANDOM_UPGRADES = RANDOM_SELECTION
# Log the refresh for debugging
import logging
logger = logging.getLogger("huntarr-sonarr")
logger.debug(f"Settings refreshed: SLEEP_DURATION={SLEEP_DURATION}, HUNT_MISSING_SHOWS={HUNT_MISSING_SHOWS}")
logger.debug(f"Advanced settings refreshed: API_TIMEOUT={API_TIMEOUT}, DEBUG_MODE={DEBUG_MODE}")
+ logger.debug(f"Random settings: RANDOM_SELECTION={RANDOM_SELECTION}, RANDOM_MISSING={RANDOM_MISSING}, RANDOM_UPGRADES={RANDOM_UPGRADES}")
def log_configuration(logger):
"""Log the current configuration settings"""
diff --git a/missing.py b/missing.py
index d515f7ee..973d5476 100644
--- a/missing.py
+++ b/missing.py
@@ -64,14 +64,13 @@ def process_missing_episodes() -> bool:
shows_processed = 0
processing_done = False
- # Optionally randomize show order - use RANDOM_MISSING setting
- # First honor the legacy RANDOM_SELECTION, then the specific RANDOM_MISSING
- should_randomize = RANDOM_SELECTION and RANDOM_MISSING
- if should_randomize:
- logger.info("Using random selection for missing shows")
+ # Use the specific RANDOM_MISSING setting
+ # (no longer dependent on the master RANDOM_SELECTION setting)
+ if RANDOM_MISSING:
+ logger.info("Using random selection for missing shows (RANDOM_MISSING=true)")
random.shuffle(shows_with_missing)
else:
- logger.info("Using sequential selection for missing shows")
+ logger.info("Using sequential selection for missing shows (RANDOM_MISSING=false)")
# Get current date for future episode filtering
current_date = datetime.datetime.now().date()
diff --git a/static/js/main.js b/static/js/main.js
index bd32719a..a30082f4 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -127,18 +127,6 @@ document.addEventListener('DOMContentLoaded', function() {
}
}
- // Sync random selection master with specific random options
- randomSelectionInput.addEventListener('change', function() {
- // If master is unchecked, disable the specific random options
- if (!this.checked) {
- randomMissingInput.disabled = true;
- randomUpgradesInput.disabled = true;
- } else {
- randomMissingInput.disabled = false;
- randomUpgradesInput.disabled = false;
- }
- });
-
// Load settings from API
function loadSettings() {
fetch('/api/settings')
@@ -164,17 +152,16 @@ document.addEventListener('DOMContentLoaded', function() {
commandWaitDelayInput.value = advanced.command_wait_delay || 1;
commandWaitAttemptsInput.value = advanced.command_wait_attempts || 600;
minimumDownloadQueueSizeInput.value = advanced.minimum_download_queue_size || -1;
- randomMissingInput.checked = advanced.random_missing !== false;
- randomUpgradesInput.checked = advanced.random_upgrades !== false;
- // Sync random selection master with specific random options
- if (!randomSelectionInput.checked) {
- randomMissingInput.disabled = true;
- randomUpgradesInput.disabled = true;
- } else {
- randomMissingInput.disabled = false;
- randomUpgradesInput.disabled = false;
- }
+ // Handle random_missing and random_upgrades
+ // If these aren't in the settings yet, default to the value of random_selection
+ randomMissingInput.checked = advanced.random_missing !== undefined
+ ? advanced.random_missing
+ : randomSelectionInput.checked;
+
+ randomUpgradesInput.checked = advanced.random_upgrades !== undefined
+ ? advanced.random_upgrades
+ : randomSelectionInput.checked;
})
.catch(error => console.error('Error loading settings:', error));
}
diff --git a/templates/index.html b/templates/index.html
index 0bef8df0..056a4d42 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -92,15 +92,15 @@
Processing Options
Only process monitored shows and episodes
-
+
-
Master switch for random selection (affects both missing and upgrades)
+
Legacy setting - Use the specific random settings below instead
-
+
Only process monitored shows and episodes
-
-
-
-
Legacy setting - Use the specific random settings below instead
-