Skip to content

Commit

Permalink
Merge pull request #70 from rfsbraz/feature/support-disk-size-filter
Browse files Browse the repository at this point in the history
feat: Add support for disk size filter (#67)
  • Loading branch information
rfsbraz authored Feb 19, 2024
2 parents cb444b0 + d100472 commit 81eb849
Show file tree
Hide file tree
Showing 18 changed files with 449 additions and 133 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/comment-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Update PR with docker image

permissions:
pull-requests: write

on:
workflow_run:
workflows: ["Create and publish a Docker image to GitHub Packages"]
types:
- completed
pull_request:
types: [opened, reopened, synchronize]

jobs:
comment-pr:
runs-on: ubuntu-latest
name: Upsert comment on the PR
steps:
- uses: thollander/actions-comment-pull-request@v2
with:
message: |
:robot: A Docker image for this PR is available to test with:
```bash
docker run -e LOG_LEVEL=DEBUG --rm -v ./config:/config -v ./logs:/config/logs ghcr.io/rfsbraz/deleterr:pr-${{ github.event.pull_request.number }}
```
This assumes you have a `config` and `logs` directory where you're running the command. You can adjust the volume mounts as needed.
comment_tag: docker_image_instructions
8 changes: 2 additions & 6 deletions .github/workflows/docker-image-dockerhub.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Create and publish a Docker image
name: Create and publish a Docker image to Dockerhub

on:
schedule:
Expand All @@ -9,10 +9,6 @@ on:
- 'develop'
tags:
- 'v*'
pull_request:
branches:
- 'master'
- 'develop'

# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
Expand Down Expand Up @@ -78,7 +74,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/docker-image-ghcr.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Create and publish a Docker image
name: Create and publish a Docker image to GitHub Packages

on:
schedule:
Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
uses: docker/build-push-action@v3
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
Expand Down
71 changes: 51 additions & 20 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from tautulli import RawAPI
from app.modules.trakt import Trakt
from app.constants import VALID_SORT_FIELDS, VALID_SORT_ORDERS, VALID_ACTION_MODES
from app.utils import validate_units


def load_config(config_file):
try:
Expand All @@ -23,7 +25,8 @@ def load_config(config_file):
logger.error(exc)

sys.exit(1)



class Config:
def __init__(self, config_file):
self.settings = config_file
Expand Down Expand Up @@ -56,7 +59,10 @@ def validate_trakt(self):
if not self.settings.get("trakt"):
return True
try:
t = Trakt(self.settings.get("trakt", {}).get("client_id"), self.settings.get("trakt", {}).get("client_secret"))
t = Trakt(
self.settings.get("trakt", {}).get("client_id"),
self.settings.get("trakt", {}).get("client_secret"),
)
t.test_connection()
return True
except Exception as err:
Expand All @@ -69,8 +75,12 @@ def validate_sonarr_and_radarr(self):
radarr_settings = self.settings.get("radarr", [])

# Check if sonarr_settings and radarr_settings are lists
if not isinstance(sonarr_settings, list) or not isinstance(radarr_settings, list):
self.log_and_exit("sonarr and radarr settings should be a list of dictionaries.")
if not isinstance(sonarr_settings, list) or not isinstance(
radarr_settings, list
):
self.log_and_exit(
"sonarr and radarr settings should be a list of dictionaries."
)

return all(
self.test_api_connection(connection)
Expand Down Expand Up @@ -109,7 +119,7 @@ def validate_tautulli(self):
)
logger.debug(f"Error: {err}")
return False

return True

def validate_libraries(self):
Expand All @@ -118,12 +128,27 @@ def validate_libraries(self):
libraries = self.settings.get("libraries", [])

if not libraries:
self.log_and_exit("No libraries configured. Please check your configuration.")

self.log_and_exit(
"No libraries configured. Please check your configuration."
)

for library in libraries:
if 'sonarr' not in library and 'radarr' not in library:
self.log_and_exit(f"Neither 'sonarr' nor 'radarr' is configured for library '{library.get('name')}'. Please check your configuration.")

if "sonarr" not in library and "radarr" not in library:
self.log_and_exit(
f"Neither 'sonarr' nor 'radarr' is configured for library '{library.get('name')}'. Please check your configuration."
)

for item in library.get("disk_size_threshold", []):
path = item.get("path")
threshold = item.get("threshold")

try:
validate_units(threshold)
except ValueError as err:
self.log_and_exit(
f"Invalid threshold '{threshold}' for path '{path}' in library '{library.get('name')}': {err}"
)

if (
len(library.get("exclude", {}).get("trakt_lists", [])) > 0
and not trakt_configured
Expand All @@ -137,16 +162,22 @@ def validate_libraries(self):
f"Invalid action_mode '{library['action_mode']}' in library '{library['name']}', it should be either 'delete'."
)

if 'watch_status' in library and library['watch_status'] not in ['watched', 'unwatched']:
if "watch_status" in library and library["watch_status"] not in [
"watched",
"unwatched",
]:
self.log_and_exit(
self.log_and_exit(
f"Invalid watch_status '{library.get('watch_status')}' in library "
f"'{library.get('name')}', it must be either 'watched', 'unwatched', "
"or not set."
)
self.log_and_exit(
f"Invalid watch_status '{library.get('watch_status')}' in library "
f"'{library.get('name')}', it must be either 'watched', 'unwatched', "
"or not set."
)
)

if 'watch_status' in library and 'apply_last_watch_threshold_to_collections' in library:
if (
"watch_status" in library
and "apply_last_watch_threshold_to_collections" in library
):
self.log_and_exit(
self.log_and_exit(
f"'apply_last_watch_threshold_to_collections' cannot be used when "
Expand All @@ -156,9 +187,9 @@ def validate_libraries(self):
)
)

if sort_config := library.get('sort', {}):
sort_field = sort_config.get('field')
sort_order = sort_config.get('order')
if sort_config := library.get("sort", {}):
sort_field = sort_config.get("field")
sort_order = sort_config.get("order")

if sort_field and sort_field not in VALID_SORT_FIELDS:
self.log_and_exit(
Expand Down
15 changes: 12 additions & 3 deletions app/constants.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
# Valid sort fields
VALID_SORT_FIELDS = ['title', 'size', 'release_year', 'runtime', 'added_date', 'rating', "seasons", "episodes"]
VALID_SORT_FIELDS = [
"title",
"size",
"release_year",
"runtime",
"added_date",
"rating",
"seasons",
"episodes",
]

# Valid sort orders
VALID_SORT_ORDERS = ['asc', 'desc']
VALID_SORT_ORDERS = ["asc", "desc"]

# Valid action modes
VALID_ACTION_MODES = ['delete']
VALID_ACTION_MODES = ["delete"]
34 changes: 33 additions & 1 deletion app/deleterr.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from app import logger
from plexapi.server import PlexServer
from plexapi.exceptions import NotFound
from app.utils import print_readable_freed_space
from app.utils import print_readable_freed_space, parse_size_to_bytes
from app.config import load_config
from pyarr.exceptions import PyarrResourceNotFound, PyarrServerError

Expand Down Expand Up @@ -106,6 +106,9 @@ def process_sonarr(self):
saved_space = 0
for library in self.config.settings.get("libraries", []):
if library.get("sonarr") == name:
if not library_meets_disk_space_threshold(library, sonarr):
continue

all_show_data = [
show
for show in unfiltered_all_show_data
Expand Down Expand Up @@ -206,6 +209,9 @@ def process_radarr(self):
saved_space = 0
for library in self.config.settings.get("libraries", []):
if library.get("radarr") == name:
if not library_meets_disk_space_threshold(library, radarr):
continue

max_actions_per_run = _get_config_value(
library, "max_actions_per_run", DEFAULT_MAX_ACTIONS_PER_RUN
)
Expand Down Expand Up @@ -687,6 +693,32 @@ def get_file_contents(file_path):
print(f"Error reading file {file_path}: {e}")


def library_meets_disk_space_threshold(library, pyarr):
for item in library.get("disk_size_threshold"):
path = item.get("path")
threshold = item.get("threshold")
disk_space = pyarr.get_disk_space()
folder_found = False
for folder in disk_space:
if folder["path"] == path:
folder_found = True
free_space = folder["freeSpace"]
logger.debug(
f"Free space for '{path}': {print_readable_freed_space(free_space)} (threshold: {threshold})"
)
if free_space > parse_size_to_bytes(threshold):
logger.info(
f"Skipping library '{library.get('name')}' as free space is above threshold ({print_readable_freed_space(free_space)} > {threshold})"
)
return False
if not folder_found:
logger.error(
f"Could not find folder '{path}' in server instance. Skipping library '{library.get('name')}'"
)
return False
return True


def main():
"""
Deleterr application entry point. Parses arguments, configs and
Expand Down
21 changes: 16 additions & 5 deletions app/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# Deleterr logger
logger = logging.getLogger("deleterr")


class LogLevelFilter(logging.Filter):
def __init__(self, max_level):
super(LogLevelFilter, self).__init__()
Expand All @@ -19,7 +20,8 @@ def __init__(self, max_level):

def filter(self, record):
return record.levelno <= self.max_level



def initLogger(console=False, log_dir=False, verbose=False):
"""
Setup logging for Deleterr. It uses the logger instance with the name
Expand Down Expand Up @@ -50,19 +52,27 @@ def initLogger(console=False, log_dir=False, verbose=False):

# Setup file logger
if log_dir:
file_formatter = logging.Formatter('%(asctime)s - %(levelname)-7s :: %(filename)s :: %(name)s : %(message)s', '%Y-%m-%d %H:%M:%S')
file_formatter = logging.Formatter(
"%(asctime)s - %(levelname)-7s :: %(filename)s :: %(name)s : %(message)s",
"%Y-%m-%d %H:%M:%S",
)

# Main logger
filename = os.path.join(log_dir, FILENAME)
file_handler = handlers.RotatingFileHandler(filename, maxBytes=MAX_SIZE, backupCount=MAX_FILES, encoding='utf-8')
file_handler = handlers.RotatingFileHandler(
filename, maxBytes=MAX_SIZE, backupCount=MAX_FILES, encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(file_formatter)

logger.addHandler(file_handler)

# Setup console logger
if console:
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(filename)s :: %(name)s : %(message)s', '%Y-%m-%d %H:%M:%S')
console_formatter = logging.Formatter(
"%(asctime)s - %(levelname)s :: %(filename)s :: %(name)s : %(message)s",
"%Y-%m-%d %H:%M:%S",
)

stdout_handler = logging.StreamHandler(sys.stdout)
stdout_handler.setFormatter(console_formatter)
Expand All @@ -76,11 +86,12 @@ def initLogger(console=False, log_dir=False, verbose=False):
logger.addHandler(stdout_handler)
logger.addHandler(stderr_handler)


# Expose logger methods
# Main logger
info = logger.info
warn = logger.warning
error = logger.error
debug = logger.debug
warning = logger.warning
exception = logger.exception
exception = logger.exception
2 changes: 1 addition & 1 deletion app/modules/tautulli.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,5 @@ def _prepare_activity_entry(self, entry, metadata):
return {
"last_watched": datetime.fromtimestamp(entry["stopped"]),
"title": metadata["title"],
"year": int(metadata.get("year") or 0)
"year": int(metadata.get("year") or 0),
}
2 changes: 1 addition & 1 deletion app/modules/trakt.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _fetch_user_list_items(
f"Traktpy does not support {listname} {media_type}s. Skipping..."
)
return []

return trakt.Trakt["users/*/lists/*"].items(
username,
listname,
Expand Down
Loading

0 comments on commit 81eb849

Please sign in to comment.