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

-