diff --git a/config/config.conf-Example b/config/config.conf-Example index bd324aa..547d368 100644 --- a/config/config.conf-Example +++ b/config/config.conf-Example @@ -9,8 +9,10 @@ REMOVE_STALLED = True REMOVE_METADATA_MISSING = True REMOVE_ORPHANS = True REMOVE_UNMONITORED = True +MIN_DOWNLOAD_SPEED = 0 PERMITTED_ATTEMPTS = 3 NO_STALLED_REMOVAL_QBIT_TAG = Don't Kill If Stalled +NO_SLOW_REMOVAL_QBIT_TAG = Don't Kill If Slow [radarr] RADARR_URL = http://radarr:7878 diff --git a/config/config.conf-Explained b/config/config.conf-Explained index 506b1ce..d087e44 100644 --- a/config/config.conf-Explained +++ b/config/config.conf-Explained @@ -50,6 +50,17 @@ REMOVE_FAILED = False # Is Mandatory: No (Defaults to False) REMOVE_STALLED = False +###### MIN_DOWNLOAD_SPEED ###### +# Sets the minimum download speed for active downloads. +# If the increase in the downloaded file size of a download is less than this value between two consecutive checks, the download is considered slow and is removed. +# Only applies to downloads that are not yet complete. +# Slow downloads are added to the blocklist, so that they are not re-requested in the future +# A new download from another source is automatically added by sonarr/radarr (if available) +# Type: Integer +# Unit: KBytes per second +# Is Mandatory: No (Defaults to 0, which means this feature is turned off) +MIN_DOWNLOAD_SPEED = 0 + ###### REMOVE_METADATA_MISSING ###### # Steers whether downloads stuck obtaining meta data are removed from the queue # These downloads are added the blocklist, so that they are not re-requested in the future diff --git a/config/config.py b/config/config.py index 01feeba..aefd3b9 100644 --- a/config/config.py +++ b/config/config.py @@ -86,8 +86,10 @@ REMOVE_STALLED = get_config_value('REMOVE_STALLED', REMOVE_METADATA_MISSING = get_config_value('REMOVE_METADATA_MISSING', 'features', False, bool, False) REMOVE_ORPHANS = get_config_value('REMOVE_ORPHANS' , 'features', False, bool, False) REMOVE_UNMONITORED = get_config_value('REMOVE_UNMONITORED' , 'features', False, bool, False) +MIN_DOWNLOAD_SPEED = get_config_value('MIN_DOWNLOAD_SPEED', 'features', False, int, 0) PERMITTED_ATTEMPTS = get_config_value('PERMITTED_ATTEMPTS', 'features', False, int, 3) NO_STALLED_REMOVAL_QBIT_TAG = get_config_value('NO_STALLED_REMOVAL_QBIT_TAG', 'features', False, str, 'Don\'t Kill If Stalled') +NO_SLOW_REMOVAL_QBIT_TAG = get_config_value('NO_SLOW_REMOVAL_QBIT_TAG', 'features', False, str, 'Don\'t Kill If Slow') # Radarr RADARR_URL = get_config_value('RADARR_URL', 'radarr', False, str) diff --git a/docker/Sample docker-compose.yml b/docker/Sample docker-compose.yml index 4d2b494..55bbcce 100644 --- a/docker/Sample docker-compose.yml +++ b/docker/Sample docker-compose.yml @@ -17,8 +17,10 @@ REMOVE_METADATA_MISSING: True REMOVE_ORPHANS: True REMOVE_UNMONITORED: True + MIN_DOWNLOAD_SPEED: 0 PERMITTED_ATTEMPTS: 3 NO_STALLED_REMOVAL_QBIT_TAG: Don't Kill If Stalled + NO_SLOW_REMOVAL_QBIT_TAG: Don't Kill If Slow # Radarr RADARR_URL: http://radarr:7878 RADARR_KEY: $RADARR_API_KEY diff --git a/main.py b/main.py index a2cb61d..54fb116 100644 --- a/main.py +++ b/main.py @@ -52,6 +52,7 @@ async def main(): logger.info('*** Current Settings ***') logger.info('%s | Removing failed downloads', str(settings_dict['REMOVE_FAILED'])) logger.info('%s | Removing stalled downloads', str(settings_dict['REMOVE_STALLED'])) + logger.info('%s | Removing slow downloads', str(settings_dict['MIN_DOWNLOAD_SPEED']) + 'KB/s') logger.info('%s | Removing downloads missing metadata', str(settings_dict['REMOVE_METADATA_MISSING'])) logger.info('%s | Removing orphan downloads', str(settings_dict['REMOVE_ORPHANS'])) logger.info('%s | Removing downloads belonging to unmonitored TV shows/movies', str(settings_dict['REMOVE_UNMONITORED'])) diff --git a/src/queue_cleaner.py b/src/queue_cleaner.py index d57d809..6f4672f 100644 --- a/src/queue_cleaner.py +++ b/src/queue_cleaner.py @@ -6,6 +6,8 @@ import json from src.utils.nest_functions import (add_keys_nested_dict, nested_get) import sys, os +download_sizes = {} + class Deleted_Downloads: # Keeps track of which downloads have already been deleted (to not double-delete) def __init__(self, dict): @@ -141,6 +143,40 @@ async def remove_unmonitored(settings_dict, BASE_URL, API_KEY, deleted_downloads logger.debug('remove_unmonitored/queue OUT: %s', str(await get_queue(BASE_URL, API_KEY) )) return len(unmonitoredItems) +async def remove_slow(settings_dict, BASE_URL, API_KEY, deleted_downloads, defective_tracker, download_sizes): + # Detects slow downloads and triggers delete. Adds to blocklist + queue = await get_queue(BASE_URL, API_KEY) + if not queue: return 0 + logger.debug('remove_slow/queue: %s', str(queue)) + if settings_dict['QBITTORRENT_URL']: + protected_dowloadItems = await rest_get(settings_dict['QBITTORRENT_URL']+'/torrents/info',params={'tag': settings_dict['NO_SLOW_REMOVAL_QBIT_TAG']}, cookies=settings_dict['QBIT_COOKIE'] ) + protected_downloadIDs = [str.upper(item['hash']) for item in protected_dowloadItems] + else: + protected_downloadIDs = [] + slowItems = [] + already_detected = [] + + for queueItem in queue['records']: + if 'downloadId' in queueItem and 'size' in queueItem and 'sizeleft' in queueItem and 'status' in queueItem: + downloaded_size = int((queueItem['size'] - queueItem['sizeleft']) / 1000) + if queueItem['status'] != 'completed' and \ + queueItem['downloadId'] in download_sizes and \ + (downloaded_size - download_sizes[queueItem['downloadId']]) / (settings_dict['REMOVE_TIMER'] * 60) < settings_dict['MIN_DOWNLOAD_SPEED']: + if queueItem['downloadId'] in protected_downloadIDs: + if queueItem['downloadId'] not in already_detected: + already_detected.append(queueItem['downloadId']) + logger.verbose('>>> Detected slow download, tagged not to be killed: %s',queueItem['title']) + else: + slowItems.append(queueItem) + try: + logger.verbose(f'{(downloaded_size - download_sizes[queueItem["downloadId"]]) * settings_dict["REMOVE_TIMER"] / 60}, : {queueItem["title"]}') + except: pass + download_sizes[queueItem['downloadId']] = downloaded_size + await check_permitted_attempts(settings_dict, slowItems, 'slow', True, deleted_downloads, BASE_URL, API_KEY, defective_tracker) + queue = await get_queue(BASE_URL, API_KEY) + logger.debug('remove_slow/queue OUT: %s', str(queue)) + return len(slowItems) + async def check_permitted_attempts(settings_dict, current_defective_items, failType, blocklist, deleted_downloads, BASE_URL, API_KEY, defective_tracker): # Checks if downloads are repeatedly found as stalled / stuck in metadata and if yes, deletes them # 1. Create list of currently defective @@ -219,7 +255,11 @@ async def queue_cleaner(settings_dict, arr_type, defective_tracker): items_detected = 0 #items_detected += await test_remove_ALL( settings_dict, BASE_URL, API_KEY, deleted_downloads, defective_tracker) - + + global download_sizes + if settings_dict['MIN_DOWNLOAD_SPEED'] > 0: + items_detected += await remove_slow( settings_dict, BASE_URL, API_KEY, deleted_downloads, defective_tracker, download_sizes) + if settings_dict['REMOVE_FAILED']: items_detected += await remove_failed( settings_dict, BASE_URL, API_KEY, deleted_downloads)