Skip to content

Commit

Permalink
New feature: Skipping files that have less than 100% availabiltiy
Browse files Browse the repository at this point in the history
  • Loading branch information
ManiMatter committed Aug 20, 2024
1 parent 7ae0ed5 commit fba4c77
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 80 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Feature overview:
- Automatically delete slow downloads, after they have been found to be slow multiple times in a row (& trigger download from another source)
- Automatically delete downloads belonging to radarr/sonarr/etc. items that are unmonitored
- Automatically delete downloads that failed importing since they are not a format upgrade (i.e. a better version is already present)
- Automatically set file to not download if they are not 100% available (missing peers)

You may run this locally by launching main.py, or by pulling the docker image.
You can find a sample docker-compose.yml in the docker folder.
Expand Down Expand Up @@ -72,6 +73,7 @@ services:
- REMOVE_SLOW=True
- REMOVE_STALLED=True
- REMOVE_UNMONITORED=True
- CANCEL_UNAVAILABLE_FILES=True
- MIN_DOWNLOAD_SPEED=100
- PERMITTED_ATTEMPTS=3
- NO_STALLED_REMOVAL_QBIT_TAG=Don't Kill
Expand Down Expand Up @@ -212,6 +214,15 @@ Steers which type of cleaning is applied to the downloads queue
- Permissible Values: True, False
- Is Mandatory: No (Defaults to False)

**CANCEL_UNAVAILABLE_FILES**
- Steers whether files within torrents are marked as 'not download' if they have less then 100% availabiltiy
- These overall download is not removed and will complete for the other files
- After import, the *arr app will trigger a search for the files that were not downloaded
- Note that this is only supported when qBittorrent is configured in decluttarr
- Type: Boolean
- Permissible Values: True, False
- Is Mandatory: No (Defaults to 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 if happening more ofthen than the permitted attempts
Expand Down
1 change: 1 addition & 0 deletions config/config.conf-Example
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ REMOVE_ORPHANS = True
REMOVE_SLOW = True
REMOVE_STALLED = True
REMOVE_UNMONITORED = True
CANCEL_UNAVAILABLE_FILES = True
MIN_DOWNLOAD_SPEED = 100
PERMITTED_ATTEMPTS = 3
NO_STALLED_REMOVAL_QBIT_TAG = Don't Kill
Expand Down
3 changes: 2 additions & 1 deletion config/definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
REMOVE_ORPHANS = get_config_value('REMOVE_ORPHANS' , 'features', False, bool, False)
REMOVE_SLOW = get_config_value('REMOVE_SLOW' , 'features', False, bool, False)
REMOVE_STALLED = get_config_value('REMOVE_STALLED', 'features', False, bool, False)
REMOVE_UNMONITORED = get_config_value('REMOVE_UNMONITORED' , 'features', False, bool, False)
REMOVE_UNMONITORED = get_config_value('REMOVE_UNMONITORED', 'features', False, bool, False)
CANCEL_UNAVAILABLE_FILES = get_config_value('CANCEL_UNAVAILABLE_FILES', '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')
Expand Down
9 changes: 7 additions & 2 deletions src/decluttarr.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from src.jobs.remove_slow import remove_slow
from src.jobs.remove_stalled import remove_stalled
from src.jobs.remove_unmonitored import remove_unmonitored
from src.jobs.cancel_unavailable_files import cancel_unavailable_files
from src.utils.trackers import Deleted_Downloads

async def queueCleaner(settingsDict, arr_type, defective_tracker, download_sizes_tracker, protectedDownloadIDs, privateDowloadIDs):
Expand Down Expand Up @@ -46,7 +47,8 @@ async def queueCleaner(settingsDict, arr_type, defective_tracker, download_sizes

# Cleans up the downloads queue
logger.verbose('Cleaning queue on %s:', NAME)

# Refresh queue:

full_queue = await get_queue(BASE_URL, API_KEY, params = {full_queue_param: True})
if not full_queue:
logger.verbose('>>> Queue is empty.')
Expand All @@ -57,7 +59,10 @@ async def queueCleaner(settingsDict, arr_type, defective_tracker, download_sizes

deleted_downloads = Deleted_Downloads([])
items_detected = 0
try:
try:
if settingsDict['CANCEL_UNAVAILABLE_FILES']:
await cancel_unavailable_files( settingsDict, BASE_URL, API_KEY, NAME, protectedDownloadIDs, privateDowloadIDs, arr_type)

if settingsDict['REMOVE_FAILED']:
items_detected += await remove_failed( settingsDict, BASE_URL, API_KEY, NAME, deleted_downloads, defective_tracker, protectedDownloadIDs, privateDowloadIDs)

Expand Down
50 changes: 50 additions & 0 deletions src/jobs/cancel_unavailable_files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from src.utils.shared import (errorDetails, formattedQueueInfo, get_queue, privateTrackerCheck, protectedDownloadCheck, execute_checks, permittedAttemptsCheck, remove_download, qBitOffline)
import sys, os, traceback
import logging, verboselogs
logger = verboselogs.VerboseLogger(__name__)
from src.utils.rest import rest_get, rest_post


async def cancel_unavailable_files(settingsDict, BASE_URL, API_KEY, NAME, protectedDownloadIDs, privateDowloadIDs, arr_type):
# Checks if downloads have less than 100% availability and marks the underyling files that cause it as 'do not download'
# Only works in qbit
try:
failType = '>100% availability'
queue = await get_queue(BASE_URL, API_KEY)
logger.debug('CANCEL_UNAVAILABLE_FILES/queue IN: %s', formattedQueueInfo(queue))
if not queue: return 0
if await qBitOffline(settingsDict, failType, NAME): return
# Find items affected

qbitHashes = list(set(queueItem['downloadId'].upper() for queueItem in queue['records']))

# Remove private and protected trackers
if settingsDict['IGNORE_PRIVATE_TRACKERS']:
for qbitHash in reversed(qbitHashes):
if qbitHash in privateDowloadIDs:
qbitHashes.remove(qbitHash)

if settingsDict['IGNORE_PRIVATE_TRACKERS']:
for qbitHash in reversed(qbitHashes):
if qbitHash in privateDowloadIDs:
qbitHashes.remove(qbitHash)

qbitItems = await rest_get(settingsDict['QBITTORRENT_URL']+'/torrents/info',params={'hashes': ('|').join(qbitHashes)}, cookies=settingsDict['QBIT_COOKIE'])

for qbitItem in qbitItems:
if 'state' in qbitItem and 'availability' in qbitItem:
if qbitItem['state'] == 'downloading' and qbitItem['availability'] < 1:
logger.info('>>> Detected %s: %s', failType, qbitItem['name'])
logger.verbose('>>>>> Marking following files to "not download":')
qbitItemFiles = await rest_get(settingsDict['QBITTORRENT_URL']+'/torrents/files',params={'hash': qbitItem['hash']}, cookies=settingsDict['QBIT_COOKIE'])
for qbitItemFile in qbitItemFiles:
if all(key in qbitItemFile for key in ['availability', 'progress', 'priority', 'index', 'name']):
if qbitItemFile['availability'] < 1 and qbitItemFile['progress'] < 1 and qbitItemFile['priority'] != 0:
logger.verbose('>>>>> %s', qbitItemFile['name'].split('/')[-1])
if not settingsDict['TEST_RUN']:
await rest_post(url=settingsDict['QBITTORRENT_URL']+'/torrents/filePrio', data={'hash': qbitItem['hash'].lower(), 'id': qbitItemFile['index'], 'priority': 0}, cookies=settingsDict['QBIT_COOKIE'])

except Exception as error:
errorDetails(NAME, error)
return

1 change: 1 addition & 0 deletions src/utils/loadScripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ def showSettings(settingsDict):
logger.info('%s | Removing slow downloads (%s)', str(settingsDict['REMOVE_SLOW']), 'REMOVE_SLOW')
logger.info('%s | Removing stalled downloads (%s)', str(settingsDict['REMOVE_STALLED']), 'REMOVE_STALLED')
logger.info('%s | Removing downloads belonging to unmonitored items (%s)', str(settingsDict['REMOVE_UNMONITORED']), 'REMOVE_UNMONITORED')
logger.info('%s | Cancelling files with >100%% availability (%s)', str(settingsDict['CANCEL_UNAVAILABLE_FILES']), 'CANCEL_UNAVAILABLE_FILES')
logger.info('')
logger.info('Running every: %s', fmt.format(rd(minutes=settingsDict['REMOVE_TIMER'])))
if settingsDict['REMOVE_SLOW']:
Expand Down
77 changes: 0 additions & 77 deletions src/utils/main.py

This file was deleted.

0 comments on commit fba4c77

Please sign in to comment.